我有一个java项目,它使用URLClassLoader在运行时从另一个jar文件加载类,就像一个插件系统。
让我给你一个问题的简化版本:让我们说在我的main方法中我将创建ClassLoader,将它作为父类加载器传递给它getClass().getClassLoader()
并从jar中加载我的插件类。
在main方法中,我创建了类的实例inst
,然后将其传递给新线程。这个新线程调用inst.getObject()
,这是我定义的方法。
现在,getObject()
通过Builder
在jar中创建另一个类new
的实例 - 假设URLClassLoader现在将加载此类,并且它是当前的定义类加载器类。这里,如果从线程调用方法,则NoClassDefFoundError
抛出Builder
,但是从main方法调用时不会抛出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
:
System.out.println(getClass().getClassLoader().toString())
当我将getObject()
置于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还是从线程调用方法,输出都完全相同。
关于为什么会发生这种情况的任何想法?以下是一些示例代码:
插件(在plugin.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();
}
}
主应用程序(在runnable app.jar中):
Builder
我目前的解决方法是在加载插件类后立即强制加载public class Plugin {
static
{
try
{
Class.forName("testapp.testplugin.Builder");
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
[...]
}
类:
sqlcmd
答案 0 :(得分:1)
在你自己的评论中已经有了答案(非常感谢你,顺便说一下),但是很难找到它,所以我在这里引用它:
好。解决了它。我在主应用程序中关闭了ClassLoader,因为我将它放在try语句中的AutoClosable包装类中。改变了这个,现在它起作用了。 - RenWal 3月15日20:49
如果您决定在执行加载的类后关闭类加载器,我建议使用java.lang.ThreadGroup。方法和所有派生线程结束:
...
Class mainClass = customClassLoader.findClass(name);
ThreadGroup threadGroup = new ThreadGroup("Custom thread group");
Thread thread = new Thread(threadGroup, new Runnable() {
@Override
public void run() {
try {
mainClass.getMethod("main", ...).invoke(...);
} catch (Throwable e) {
// exception handling
}
}
});
thread.start();
while (threadGroup.activeCount() > 0) {
Thread.sleep(100);
}
customClassLoader.close();
在线程上下文中创建的所有线程和线程组将直接或间接属于threadGroup。因此,我们可以等到活动线程数变为零。
<强> UPD 即可。当然,如果例如,那将无法保存。调用ExecutorService,其任务需要加载类,或者注册一个监听器,因此代码将脱离线程组。因此,在一般情况下,关闭类加载器只能在JVM出口上安全。