简体   繁体   中英

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. I'm looking for jar files in a directory and add them to the classpath in order to use them as JDBC drivers.

After doing so I can easily use 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.

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

Unfortunately for this approach I'd need the drivers class name, ie "org.postgresql.Driver" which I don't know.

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. 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

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. (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.

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!)). 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. You'll probably want to change the URL - you'll need the Scout class and the driver 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):

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 .

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. I have same problem with dynamically load driver, because of jars are conflict. Even though, I have to know the driver name to jar, which i want to load use my url class loader.

DriverManager use class loader to load jar, so it could find jdbc driver by name. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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