I have a java project that uses a URLClassLoader to load classes from another jar file at runtime, like a plugin system.
Let me give you a simplified version of the problem: Let's say that in my main method I would create the ClassLoader, pass it getClass().getClassLoader()
as the parent class loader and load my plugin class from the jar.
In the main method, I create an instance inst
of the class and then pass it to a new thread. This new thread calls inst.getObject()
, which is a method I defined.
Now, getObject()
creates an instance of another class Builder
in the jar via new
- assuming the URLClassLoader would now load this class as well as it is the defining classloader of the current class. Here, a NoClassDefFoundError
is thrown for Builder
if the method is invoked from the thread, but not when invoked from the main method:
Exception in thread "Thread-0" java.lang.NoClassDefFoundError: testapp/testplugin/Builder
at testapp.testplugin.Plugin.getObject(Plugin.java:88)
at testapp.mainapp.TestInit$1.run(TestInit.java:90)
Caused by: java.lang.ClassNotFoundException: testapp.testplugin.Builder
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.net.FactoryURLClassLoader.loadClass(URLClassLoader.java:814)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 7 more
When I put System.out.println(getClass().getClassLoader().toString())
inside getObject()
, the output is exactly the same whether I call the method from main or from the thread.
Any ideas as to why this happens? Here is some sample code:
Plugin (in plugin.jar):
package testapp.testplugin;
// Pluggable defines the getObject() method, common interface for all plugins
public class Plugin implements Pluggable{
Builder build;
public Plugin() {
// set some fields
}
@Override
public Object getObject()
{
// lazy initialisation for "build"
if (build == null)
build = new Builder(); ///// !NoClassDefFoundError! /////
// make Builder assemble an object and return it
return build.buildObject();
}
}
Main application (in runnable app.jar):
package testapp.mainapp;
public class TestInit {
public static void main(String[] args) throws Exception {
// create URLClassLoader
URLClassLoader clazzLoader = URLClassLoader.newInstance(new URL[]{new URL("testplugin.jar"},
getClass().getClassLoader());
// load plugin class
Class<?> clazz = Class.forName("testapp.testplugin.Plugin", true, clazzLoader);
Class<? extends Pluggable> subClazz = clazz.asSubclass(Pluggable.class);
// instantiate plugin class using constructor (to avoid Class.newInstance())
Constructor<? extends Pluggable> constr = subClazz.getConstructor();
final Pluggable plugin = constr.newInstance();
// create new thread and run getObject()
Thread t = new Thread(){
@Override
public void run() {
// something more sophisticated in the real application, but this is enough to reproduce the error
System.out.println(plugin.getObject());
}
};
t.start();
}
}
My current workaround is to force-load the Builder
class as soon as the plugin class is loaded:
public class Plugin {
static
{
try
{
Class.forName("testapp.testplugin.Builder");
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
[...]
}