I’ve written before about using classloaders to isolate portions of your code. Lately, I’ve been in the thick of fighting some classloader issues so I thought I would add a few additional thoughts.
Post-delegation resource loading
One thing I did not mention in my previous post on writing a post-delegation classloader is that it is also essential to implement post-delegation resource loading. Every time I write one of these classloaders, I forget to do that and I end up debugging some nasty issue in a third party library (like a JDBC driver, app server lib, etc) at a most inconvenient time (like right before a release). So, you’ll want to add a method like this to your post-delegation classloader: [source:java]
public URL getResource(String name) {
// Try to find it locally first
URL url = findResource(name);</p>
// If not, try parent
if(url == null) {
url = super.findResource(name);
}
return url;
}
[/source]
Simple, but essential. Note to future me: don’t forget to do this next time!!
Linkage errors
Inevitably, if you do enough classloader work, you’ll hit the dreaded LinkageError. A LinkageError indicates a violation in the loading constraints. When using a post-delegation classloader, you will typically see LinkageErrors when both the main classloader and the plugin classloader load the same class AND the plugin passes that class back through the plugin interface. This will generally result in LinkageError and typically a stream of profanity (maybe that’s just me).
When using post-delegation, you need to ensure that any class that can be passed through your plugin interface (including exceptions!!) is defined only in your main classloader. You cannot define them in your plugin classloader as they will be loaded from there first due to post-delegation.
If you are using normal parent delegation, it’s perfectly ok to define the same class in both classloaders. Due to parent delegation, the main classloader’s version is always the one that will be used, even in the plugin’s domain.
Thread context classloader
Another tricky thing the first time you hit it is the thread context classloader. You can get and set the context classloader on java.lang.Thread via the getContextClassLoader() and setContextClassLoader() methods. When a Thread is created, it’s context classloader is set from the parent Thread. So, in the simplest case, most Threads end up with the system classloader as their context classloader.
The thread context classloader provides a way to pass an “intended classloader parent” into a framework without explicitly needing to pass it. However, Java frameworks do not follow consistent patterns for classloading. Some use the thread context classloader, some use the current classloader (the one that defined the current class), some accept an explicit ClassLoader argument, and some use a mixture of these approaches (with varying orderings).
The key thing to know though is that many common and important frameworks DO use the thread context classloader (JMX, JAXP, JNDI, etc). If you are using a J2EE application server, you are almost certainly relying on code using the thread context classloader.
So, if you have a plugin in a classloader (regardless of whether you are using post-delegation), it is a really good idea to wrap calls to the plugin interface and set the thread context classloader. Also important is that you need to remember the original context classloader and re-set it when you return from the plugin call. Otherwise, you have possibly installed a time bomb in the current thread. If the same thread later calls some other code that relies on the context classloader and that code is not written to properly set the context classloader, then that code will fail. This is particularly bad because it could occur at a much later point, far removed from the original failure location.
The easiest way to handle this problem is to use a dynamic proxy. This allows you to wrap all calls to a plugin interface with a single generic dynamic proxy. So, you might want to use something like this: [source:java]
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ClassLoaderInjectorHandler implements InvocationHandler {
private final Object proxyInstance;
private final ClassLoader injectedClassLoader;
public ClassLoaderInjectorHandler(Object instance, ClassLoader classLoader) {
proxyInstance = instance;
injectedClassLoader = classLoader;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Remember original thread context classloader
ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();
try {
// Set with the injected classloader
Thread.currentThread().setContextClassLoader(injectedClassLoader);
// Invoke the method on the proxy instance
return method.invoke(proxyInstance, args);
} finally {
// Replace the original classloader on the way out
Thread.currentThread().setContextClassLoader(originalLoader);
}
}
public static Object wrapClassLoaderInjector(Object instance, ClassLoader classLoader, Class<?>[] interfaces) {
InvocationHandler handler = new ClassLoaderInjectorHandler(instance, classLoader);
return Proxy.newProxyInstance(classLoader, interfaces, handler);
}
}
[/source]
The static method at the end is a helper method that can wrap any interface with a dynamic proxy that will inject the thread context classloader on all methods on an interface and replace the context classloader on the way out with a call like this (exception handling omitted): [source:java]
// Start with class name and a classloader
String className = “ExamplePluginImplementation”;
ClassLoader classLoader = …construct plugin classloader…
// Instantiate the plugin in the classloader
Class<?> pluginClass = classLoader.loadClass(className);
Plugin plugin = pluginClass.newInstance();
// Wrap the plugin instance in the dynamic proxy
plugin = (Plugin) ClassLoaderInjectorHandler.wrapClassLoaderInjector(
plugin, classLoader,
new Class<?>[] { Plugin.class } );
[/source]
I’ve found that not setting the thread context classloader can result in some error situations that can be maddening to track down. A great technique for doing so is to modify your classloader loadClass() method to print each class that is being loaded and whether or not it succeeded.
Generally, when you see something funky, there was a class loaded (or not loaded) in the region right before. It’s also extremely helpful to run in both a working and failing classloader situation (for example putting all your jars on the main classloader), and then diffing the output.
For instance, I tracked down a problem like this today – I was seeing a JMX error due to a missing protocol. In the working version of my environment, I could see the protocol class getting loaded and not getting loaded in the failing version. I took a look at the JMXConnectionFactory and saw that you can either set the context classloader or set a property when passing in the Context. I applied my wrapper dynamic proxy and things started working.