» Loading a JDBC Driver at runtime
      In the context of a nice mini-framework (just a few classes actually) for integration tests for Java using JUnit, DBUnit (for database initialisation and comparison against expected data) and Spring, I found myself confronted with somewhat less elegant requirement of having to specify the JDBC Driver jar filename in the 
And here is the class to use to configure the actual JDBC Driver class as well as the JDBC driver jar file, shaped as a Spring-ready singleton bean:
All you need to do now is to use 
    CLASSPATH and having to change it depending on the target database. Whereas changing the JDBC database URL, username and password is easy (just using Spring's PropertyPlaceholderConfigurer and a .properties file), changing the CLASSPATH is annoying, because it has to be changed in the Eclipse build path (e.g. using a classpath variable) as well as in the build configuration (be it Ant or Maven).
I wanted to specify the filename of the JDBC Driver jar in the same .properties file as the JDBC URL, username and password.
While this may sound trivial to some, it isn't, because you cannot load jars at runtime using the default ClassLoader.
This code snippet shows how one can (ab)use the URLClassLoader to load jars at runtime.
But the problem is that I didn't want to set a new default ClassLoader nor pass JVM parameters at startup, i.e. use the pristine Eclipse and Ant/Maven environment and do it purely through Java code at runtime.
The trick is quite simple, actually:
1) write a delegate implementation of JDBC's java.sql.Driver class, that passes each method call to a static (singleton) Driver
2) use the name of the delegate class above as the name of the JDBC Driver class
3) set the static Driver in the delegate Driver class above to an instance of the real JDBC Driver class
Let's start with the delegate:
package sample;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.util.Properties;
public class DelegateDriver implements Driver {
    
    static {
        try {
            DriverManager.registerDriver(new DelegateDriver());
        } catch (SQLException e) {
            throw new RuntimeException(new StringBuffer()
            .append("failed to register ").append(DelegateDriver.class.getName())
            .append(" with the JDBC ").append(DriverManager.class.getName())
            .append(": ").append(e.getMessage()).toString(), e);
        }
    }
    
    public static Driver DELEGATE = null;
    private static Driver getDelegate() {
        if (DELEGATE == null) {
            throw new IllegalStateException("delegate driver not set");
        }
        return DELEGATE;
    }
    
    public boolean acceptsURL(String url) throws SQLException {
        return getDelegate().acceptsURL(url);
    }
    public Connection connect(String url, Properties info) throws SQLException {
        return getDelegate().connect(url, info);
    }
    public int getMajorVersion() {
        return getDelegate().getMajorVersion();
    }
    public int getMinorVersion() {
        return getDelegate().getMinorVersion();
    }
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info)
            throws SQLException {
        return getDelegate().getPropertyInfo(url, info);
    }
    public boolean jdbcCompliant() {
        return getDelegate().jdbcCompliant();
    }
}package sample;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Driver;
public class JDBCDriverLoader {
    
    private String jdbcDriverClass;
    private File jdbcDriverFile;
    
    /** Configure using Spring or Java code: */
    public void setJdbcDriverFile(File jdbcDriverFile) {
        this.jdbcDriverFile = jdbcDriverFile;
    }
    
    /** Configure using Spring or Java code: */
    public void setJdbcDriverClass(String jdbcDriverClass) {
        this.jdbcDriverClass = jdbcDriverClass;
    }
    public void initialize() throws Exception {
        // TODO throw IllegalStateException if jdbcDriverFile or jdbcDriverClass is null
        DelegateDriver.DELEGATE = (Driver) new URLClassLoader(new URL[]{}, this.getClass().getClassLoader()) {{
            // Have to use a subclass because addURL() is protected.
            // See http://snippets.dzone.com/posts/show/3574
            addURL(new URL("jar:file://" + jdbcDriverFile.getPath() + "!/"));
        }}.loadClass(jdbcDriverClass).newInstance();
    }
    
}sample.DelegateDriver as the name of the JDBC Driver class (e.g. in your Apache Commons DBCP connection pool).
Jumping through those hoops is needed because it's a different ClassLoader.
I'll leave the rest of the glue as an exercise to the reader ;)Labels: java








