» 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