简体   繁体   English

自 Java 9 以来,如何在运行时动态加载 JDBC 驱动程序?

[英]How to load a JDBC driver dynamically during runtime since Java 9?

I'm currently migrating my Java 8 code to Java 11 and stumbled across a problem.我目前正在将我的 Java 8 代码迁移到 Java 11 并偶然发现了一个问题。 I'm looking for jar files in a directory and add them to the classpath in order to use them as JDBC drivers.我正在目录中查找 jar 文件并将它们添加到类路径中,以便将它们用作 JDBC 驱动程序。

After doing so I can easily use DriverManager.getConnection(jdbcString);这样做之后,我可以轻松地使用DriverManager.getConnection(jdbcString); to get a connection to any database I loaded a driver beforehand.为了连接到任何数据库,我事先加载了一个驱动程序。

I used to load drivers using this bit of code which no longer works since the SystemClassLoader is no longer a URLClassLoader.我曾经使用这段代码加载驱动程序,但由于 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 after looking around for alternatives I found this answer on SO: https://stackoverflow.com/a/14479658/10511969因此,在寻找替代方案后,我在 SO 上找到了这个答案: https : //stackoverflow.com/a/14479658/10511969

Unfortunately for this approach I'd need the drivers class name, ie "org.postgresql.Driver" which I don't know.不幸的是,对于这种方法,我需要驱动程序类名称,即我不知道的“org.postgresql.Driver”。

Is there just no way to do this anymore, or am I missing something?有没有办法再这样做了,或者我错过了什么?

Using a Shim is a good way to load the JDBC driver when the driver is, for some reason, not accessibile via the system class loader context.当驱动程序由于某种原因无法通过系统类加载器上下文访问时,使用 Shim 是加载 JDBC 驱动程序的好方法。 I have ran into this a few times with multi-threaded scripts that have their own separated classpath context.我已经用多线程脚本遇到过几次这种情况,这些脚本有自己分离的类路径上下文。

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

Not knowing the driver's class seems like an odd constraint.不知道司机的班级似乎是一个奇怪的限制。

I would go for a custom class loader that after ever class initialisation (I think you can do that), calls DriverManager.getDrivers and registers any new drivers it finds.我会选择一个自定义类加载器,在类初始化之后(我认为你可以这样做),调用DriverManager.getDrivers并注册它找到的任何新驱动程序。 (I have no time at the moment to write the code.) (我现在没时间写代码。)

The hacky alternative would be to load all your code (except a bootstrap) in a URLClassLoader and addURL to that. hacky 的替代方法是在URLClassLoader加载所有代码(引导程序除外)并将addURL到其中。

Edit: So I wrote some code.编辑:所以我写了一些代码。

It creates a class loader for the drivers that also contains a "scout" class that forwards DriverManager.drivers (which is a naughty caller sensitive method (a newish one!)).它为驱动程序创建了一个类加载器,该类加载器还包含一个转发DriverManager.drivers的“scout”类(这是一个顽皮的调用者敏感方法(一个新的方法!))。 A fake driver within the application class loader forwards connect attempts onto any dynamically loaded drivers at the time of request.应用程序类加载器中的虚假驱动程序在请求时将连接尝试转发到任何动态加载的驱动程序。

I don't have any JDBC 4.0 or later drivers conveniently around to test this on.我没有任何 JDBC 4.0 或更高版本的驱动程序可以方便地对其进行测试。 You'll probably want to change the URL - you'll need the Scout class and the driver jar.您可能想要更改 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"));
    }
}

Within a directory dynamic (relative to current working directory):dynamic目录中(相对于当前工作目录):

import java.sql.*;

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

I would always suggest avoiding setting the thread context class loader to anything other than a loader that denies everything, or perhaps null .我总是建议避免将线程上下文类加载器设置为拒绝一切的加载器以外的任何东西,或者null

Modules may well allow you to load drivers cleanly, but I've not looked.模块很可能允许您干净地加载驱动程序,但我没有看过。

if you don`t know the driver name, you cannot use reflect to use urlLoader to load jar, which you exactly want.如果您不知道驱动程序名称,则无法使用反射来使用 urlLoader 加载 jar,这正是您想要的。 I have same problem with dynamically load driver, because of jars are conflict.我对动态加载驱动程序有同样的问题,因为 jars 是冲突的。 Even though, I have to know the driver name to jar, which i want to load use my url class loader.尽管如此,我必须知道 jar 的驱动程序名称,我想使用我的 url 类加载器加载它。

DriverManager use class loader to load jar, so it could find jdbc driver by name. DriverManager 使用类加载器来加载 jar,因此它可以通过名称找到 jdbc 驱动程序。 As usual we use: class.forName。 We use self defined class loader to load our driver, so that it can solve the conflict of jars.像往常一样我们使用:class.forName。我们使用自定义的类加载器来加载我们的驱动程序,这样它就可以解决jar的冲突。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM