I recently discovered the usefulness of dynamically loading classes into a JVM and thought I would document the discovery.
Java has a class called a URL class loader (URLClassLoader). You can create one using a URL and install it as the current thread’s class loader. Any references to new classes will use the URL class loader first before looking at the parent class loader.
URL[] urls = ...
ClassLoader originalClassLoader = Thread.currentThread().getClassLoader();
ClassLoader newClassLoader = new URLClassLoader(urls, originalClassLoader);
try {
Thread.currentThread().setContextClassLoader(newClassLoader);
// write code to load new classes
} finally {
Thread.currentThread().setCLassLoader(originalClassLoader);
}
The example and the way I used this was for loading in JNDI initial contexts. See this tutorial for an example.
One can use this to load groups of classes together from a local jar file.
I found it useful to extend this a little with the ability to walk a file system searching for jar files to load using a single class loader.
public class JarSeekingURLClassLoader extends URLClassLoader {
public JarSeekingURLClassLoader(File file, ClassLoader parent) throws MalformedURLException {
super(makeUrls(file), parent);
}
private static URL[] makeUrls(File file) throws MalformedURLException {
List<URL> urls = new ArrayList<URL>();
urls.add(file.toURI().toURL());
File[] jarFilesAndDirs = file.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return dir.isDirectory() || name.endsWith(".jar");
}
});
if (jarFilesAndDirs != null) {
for (File jarOrDir : jarFilesAndDirs) {
if (jarOrDir.isDirectory()) {
urls.addAll(Arrays.asList(makeUrls(jarOrDir)));
} else {
urls.add(jarOrDir.toURI().toURL());
}
}
}
return urls.toArray(new URL[urls.size()]);
}
public JarSeekingURLClassLoader(File file) throws MalformedURLException {
super(makeUrls(file));
}
public JarSeekingURLClassLoader(File file, ClassLoader parent, URLStreamHandlerFactory factory) throws MalformedURLException {
super(makeUrls(file), parent, factory);
}
}
One can of course use these two patterns together to arrange to dynamically load a directory full of JAR files using a single class loader.
URL[] urls = ...
ClassLoader originalClassLoader = Thread.currentThread().getClassLoader();
ClassLoader newClassLoader = new JarSeekingURLClassLoader(new File(Config.getDynamicLibraryLocation());
try {
Thread.currentThread().setContextClassLoader(newClassLoader);
// write code to load new classes
Class.forName("com.dynamic.library.class");
} finally {
Thread.currentThread().setCLassLoader(originalClassLoader);
}
This trick is handy if you need to simplify your compile time dependencies.
This is of course getting close to building a dynamic loading system like OSGi, but sometimes small concepts are more efficient.
Did you mean to say Thread.setContextClassLoader()? That was the only method I could find: http://download.oracle.com/javase/6/docs/api/java/lang/Thread.html#setContextClassLoader(java.lang.ClassLoader).
The problem with this is that it only works for the Thread you call setContextClassLoader() on. Given the propensity to use Executors and other thread pools, what happens if the task is passed off onto another thread without the caller realizing it? (Hmmm, can you think of any problems we’ve run into in a certain application making heavy use of ThreadLocal?) This substitution really needs to happen at a global level. Do you know of a way to accomplish that?
The only thing I’ve found close to this is in the instrumentation package: http://download.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html . If you set the Premain-Class attribute in a jar’s manifest, you can gain control of class loading by having your premain() method add a ClassFileTransformer via Instrumentation.addTransformer() as per http://download.oracle.com/javase/6/docs/api/java/lang/instrument/Instrumentation.html#addTransformer(java.lang.instrument.ClassFileTransformer). However, that is meant to operate on classes that have already been found, and offers you the opportunity to transform their byte code before they are used. (This is so you can use CGI or ASM to transform the byte code for instrumentation purposes, much like the long-ago C/C++ Purify product, or the contemporary Valgrind.) At least this has a global effect. But it does require having found the class first; your example doesn’t, because your ClassLoader is effective only in case the parent one doesn’t find the target class.
(BTW, is it just me, or does it seem weird that the delegation model here is to check the parent first, and then use your ClassLoader? See the rules here: http://download.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html . I guess this mechanism isn’t meant to be used for overriding classes as the Instrumentation mechanism is, only for augmenting what the system class loader can do.)
So we’re left with the problem of how to daisy-chain your custom class loader in a global way. Any ideas?
Yes I did mean that. THis is the trouble with late night blogging without the original source code on the current laptop. Correction forthcoming and thanks for reading.
Hey Chris,
SO after some research there is no way to globally define a class loader in Java. The system class loader and the lookup are specified to load all the JVM (think java.lang) classes, and together with the “parent-first” lookup mechanism guarantees that no-one can swap in their own implementation of standard (“protected”) classes.
All class loaders (context/threaded or otherwise) all delegate to that global class loader.