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.
1
2
3
4
5
6
7
8
9
10
<br />
URL[] urls = ...<br />
ClassLoader originalClassLoader = Thread.currentThread().getClassLoader();<br />
ClassLoader newClassLoader = new URLClassLoader(urls, originalClassLoader);</p>
<p>try {<br />
Thread.currentThread().setContextClassLoader(newClassLoader);<br />
// write code to load new classes<br />
} finally {<br />
Thread.currentThread().setCLassLoader(originalClassLoader);<br />
}<br />
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<br />
public class JarSeekingURLClassLoader extends URLClassLoader {</p>
<p> public JarSeekingURLClassLoader(File file, ClassLoader parent) throws MalformedURLException {<br />
super(makeUrls(file), parent);<br />
}</p>
<p> private static URL[] makeUrls(File file) throws MalformedURLException {<br />
List<URL> urls = new ArrayList<URL>();<br />
urls.add(file.toURI().toURL());<br />
File[] jarFilesAndDirs = file.listFiles(new FilenameFilter() {<br />
@Override<br />
public boolean accept(File dir, String name) {<br />
return dir.isDirectory() || name.endsWith(".jar");<br />
}<br />
});<br />
if (jarFilesAndDirs != null) {<br />
for (File jarOrDir : jarFilesAndDirs) {<br />
if (jarOrDir.isDirectory()) {<br />
urls.addAll(Arrays.asList(makeUrls(jarOrDir)));<br />
} else {<br />
urls.add(jarOrDir.toURI().toURL());<br />
}<br />
}<br />
}<br />
return urls.toArray(new URL[urls.size()]);<br />
}</p>
<p> public JarSeekingURLClassLoader(File file) throws MalformedURLException {<br />
super(makeUrls(file));<br />
}</p>
<p> public JarSeekingURLClassLoader(File file, ClassLoader parent, URLStreamHandlerFactory factory) throws MalformedURLException {<br />
super(makeUrls(file), parent, factory);<br />
}<br />
}<br />
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.
1
2
3
4
5
6
7
8
9
10
11
<br />
URL[] urls = ...<br />
ClassLoader originalClassLoader = Thread.currentThread().getClassLoader();<br />
ClassLoader newClassLoader = new JarSeekingURLClassLoader(new File(Config.getDynamicLibraryLocation());</p>
<p>try {<br />
Thread.currentThread().setContextClassLoader(newClassLoader);<br />
// write code to load new classes<br />
Class.forName("com.dynamic.library.class");<br />
} finally {<br />
Thread.currentThread().setCLassLoader(originalClassLoader);<br />
}<br />
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.