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.