从Java 9开始,如何在运行时动态加载JDBC驱动程序?

时间:2019-01-16 12:17:00

标签: java jdbc classloader

我目前正在将Java 8代码迁移到Java 11,偶然发现一个问题。我正在目录中寻找jar文件,并将其添加到类路径中,以便将它们用作JDBC驱动程序。

这样做之后,我可以轻松地使用DriverManager.getConnection(jdbcString);来连接到我预先加载驱动程序的任何数据库。

我以前使用这段代码加载驱动程序,由于SystemClassLoader不再是URLClassLoader,因此不再起作用。

Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
method.setAccessible(true);
method.invoke(ClassLoader.getSystemClassLoader(), new Object[] { jdbcDriver.toURI().toURL() });

因此,在寻找替代方案之后,我在SO上找到了以下答案: https://stackoverflow.com/a/14479658/10511969

不幸的是,对于这种方法,我需要驱动程序类名,即我不知道的“ org.postgresql.Driver”。

难道没有办法做这个了,还是我错过了什么?

3 个答案:

答案 0 :(得分:1)

当驱动程序由于某种原因无法通过系统类加载器上下文进行访问时,使用Shim是加载JDBC驱动程序的好方法。我曾多次使用具有自己单独的类路径上下文的多线程脚本来遇到这种情况。

http://www.kfu.com/~nsayer/Java/dyn-jdbc.html

答案 1 :(得分:0)

不知道驾驶员的等级似乎是一个奇怪的限制。

我将选择一个自定义的类加载器,该类加载器在进行类初始化之后(我认为您可以这样做),调用DriverManager.getDrivers并注册找到的任何新驱动程序。 (我现在没有时间编写代码。)

骇人听闻的选择是将所有代码(引导程序除外)加载到URLClassLoaderaddURL中。

编辑:所以我写了一些代码。

它为驱动程序创建一个类加载器,该类加载器还包含转发DriverManager.drivers的“侦查”类(这是一个调皮的调用者敏感方法(一个新方法!))。在请求时,应用程序类加载器中的伪造驱动程序会将连接尝试转发到任何动态加载的驱动程序上。

我没有任何便利的JDBC 4.0或更高版本的驱动程序来对此进行测试。您可能需要更改URL-您需要Scout类和驱动程序jar。

import java.lang.reflect.*;
import java.net.*;
import java.sql.*;
import java.util.*;
import java.util.logging.*;
import java.util.stream.*;

class FakeJDBCDriver {
    public static void main(String[] args) throws Exception {
        URLClassLoader loader = URLClassLoader.newInstance(
            new URL[] { new java.io.File("dynamic").toURI().toURL() },
            FakeJDBCDriver.class.getClassLoader()
        );
        Class<?> scout = loader.loadClass("Scout");
        Method driversMethod = scout.getMethod("drivers");
        DriverManager.registerDriver(new Driver() {
            public int getMajorVersion() {
                return 0;
            }
            public int getMinorVersion() {
                return 0;
            }
            public Logger getParentLogger() throws SQLFeatureNotSupportedException {
                throw new SQLFeatureNotSupportedException();
            }
            public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
                return new DriverPropertyInfo[] { };
            }
            public boolean jdbcCompliant() {
                return false;
            }
            public boolean acceptsURL(String url) throws SQLException {
                if (url == null) {
                    throw new SQLException();
                }
                for (Iterator<Driver> iter=drivers(); iter.hasNext(); ) {
                    Driver driver = iter.next();
                    if (
                        driver.getClass().getClassLoader() == loader &&
                        driver.acceptsURL(url)
                    ) {
                        return true;
                    }
                }
                return false;
            }
            public Connection connect(String url, Properties info) throws SQLException {
                if (url == null) {
                    throw new SQLException();
                }
                for (Iterator<Driver> iter=drivers(); iter.hasNext(); ) {
                    Driver driver = iter.next();
                    if (
                        driver.getClass().getClassLoader() == loader &&
                        driver.acceptsURL(url)
                    ) {
                        Connection connection = driver.connect(url, info);
                        if (connection != null) {
                            return connection;
                        }
                    }
                }
                return null;
            }
            private Iterator<Driver> drivers() {
                try {
                    return ((Stream<Driver>)driversMethod.invoke(null)).iterator();
                } catch (IllegalAccessException exc) {
                    throw new Error(exc);
                } catch (InvocationTargetException exc) {
                    Throwable cause = exc.getTargetException();
                    if (cause instanceof Error) {
                        throw (Error)cause;
                    } else if (cause instanceof RuntimeException) {
                        throw (RuntimeException)cause;
                    } else {
                        throw new Error(exc);
                    }
                }
            }
        });

        // This the driver I'm trying to access, but isn't even in a jar.
        Class.forName("MyDriver", true, loader);

        // Just some nonsense to smoke test.
        System.err.println(DriverManager.drivers().collect(Collectors.toList()));
        System.err.println(DriverManager.getConnection("jdbc:mydriver"));
    }
}

在目录dynamic中(相对于当前工作目录):

import java.sql.*;

public interface Scout {
    public static java.util.stream.Stream<Driver> drivers() {
        return DriverManager.drivers();
    }
}

我总是建议避免将线程上下文类加载器设置为拒绝所有内容的加载器,或者设置为null以外的任何其他值。

模块很可能允许您干净地加载驱动程序,但我没有看过。

答案 2 :(得分:0)

如果您不知道驱动程序名称,则不能使用反射来使用urlLoader加载您确实想要的jar。 我与动态加载驱动程序存在相同的问题,因为jar冲突。 即使,我必须知道jar的驱动程序名称,我想使用我的url类加载器进行加载。

DriverManager使用类加载器加载jar,因此可以按名称查找jdbc驱动程序。与往常一样,我们使用:class.forName。 我们使用自定义类加载器加载驱动程序,以便解决jar冲突。