본문 바로가기

프로그래밍

Java11 에서 JDBC Jar file을 동적 로딩하는 방법

반응형

개발을 진행하면서 Java11 에서 JDBC Jar file을 동적 로딩해야 하는 이슈가 생겼다.

모든 JDBC jar 를 빌드에 넣어둘 수 없기 때문에 서버 설치 환경에 따라 특정 Path 를 지정해두고 Path 내에 존재하는 모든 Jar(JDBC 라고 가정한다) 를 읽어서 Class.forName 해주는 일.

 

Java8 에서 쓰던 코드가 있었는데 이번에 Java11 프로젝트에 같이 기능 구현하려니 동작이 안되어서... 

다시 쓸지도 모르니 정리해본다.

 

결과적으로 정리하면

1. Jar 로드

2. JDBC driver name 탐색. (jar안에 META-INF/sources/java.sql.Driver 파일 안에 text로 classname 이 적혀있다.

3. Driver 등록

 

jdbc classname은 이런식으로 들어있다.

 

Driver 등록의 경우 Java9 이후 보안이 강해지면서 ClassLoader를 사용할 수 있는 방법이 없어 DriverShim 클래스를 생성해 우회 등록했다.

 

DriverShim 클래스 내용

public class DriverShim implements Driver {
    private Driver driver;
    
    DriverShim(Driver d) {
        this.driver = d;
    }
    public boolean acceptsURL(String u) throws SQLException {
        return this.driver.acceptsURL(u);
    }
    public Connection connect(String u, Properties p) throws SQLException {
        return this.driver.connect(u, p);
    }
    public int getMajorVersion() {
        return this.driver.getMajorVersion();
    }
    public int getMinorVersion() {
        return this.driver.getMinorVersion();
    }
    public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException {
        return this.driver.getPropertyInfo(u, p);
    }
    public boolean jdbcCompliant() {
        return this.driver.jdbcCompliant();
    }
    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

 

아래는 실제 동작하는 코드.

    public void loadJdbcJars() {
        try {
            String szJars = getDriverFileNames();
            if( szJars == null) {
                System.out.println("has no JDBC jars");
                return;
            } else {
                System.out.println("loading jars : " + szJars);
            }
            String[] jars = szJars.split("\\|");
            
            for( String jar : jars) {
                if( jar != null) {
                    File jarPath = new File(getJdbcPath() + "/" + jar);
                    if (jarPath.isFile()) {
                        URL url = new URL("jar:" + jarPath.toURI().toURL() + "!/");
                        String classname = getClassNamesFromJarFile(jarPath);
                        if (classname != null) {
                            System.out.println("JDBC: " + classname);
                            URLClassLoader ucl = new URLClassLoader(new URL[] { url });
                            Driver d = (Driver)Class.forName(classname, true, ucl).getDeclaredConstructor().newInstance();
                            DriverManager.registerDriver(new DriverShim(d));
                        }
                    }
                }
            }
        } catch( Exception e) {
            throw new RuntimeException("failed to loading JDBC jars.", e);
        }
    }

    private String getDriverFileNames() {
        File dir = new File(CommonConfig.getJdbcPath());
        if( dir.isDirectory()) {
            return String.join( "|", dir.list());
        }
        return null;
    }

    private String getClassNamesFromJarFile(File givenFile) throws IOException {
        String className = null;
        try (JarFile jarFile = new JarFile(givenFile)) {
            Enumeration<JarEntry> e = jarFile.entries();
            while (e.hasMoreElements()) {
                JarEntry jarEntry = e.nextElement();
                if (jarEntry.getName().endsWith(".Driver")) {
                    InputStream input = jarFile.getInputStream(jarEntry);
                    className = readClassNameFromInputStream(input);
                    break;
                }
            }
        }
        return className;
    }

    private String readClassNameFromInputStream(InputStream is) throws IOException{
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader reader = new BufferedReader(isr);
        String line = reader.readLine();
        reader.close();
        if (line != null) line = line.trim();
        return line;
    }

 

이렇게 정리해두면 다른 프로젝트에서 쓰기 좋겠지.. 

 

반응형