加载依赖于内部类的外部类

时间:2013-09-16 17:57:48

标签: java plugins reflection classloader

我正在尝试为Java编写一个简单的插件架构(它扩展了我们现有的一些功能,这就是为什么我没有使用正确的插件库)

我的战争中有一个基类(使用JBoss部署):

package org.example.components;

public abstract class ComponentBase {
    // Base code
}

我有一个包含一些自定义组件代码的jar custom-components.jar

package org.example.components.custom;

public class CustomComponent extends ComponentBase {
    // Custom code
}

通过一些魔法,我的战争中的插件代码获得了CustomComponent类的名称并且确实:

Class classToRegister = Class.forName("org.example.components.custom.CustomComponent");

然而,这会引发NoClassDefFoundError,说它无法找到org.example.components.ComponentBase

Caused by: java.lang.NoClassDefFoundError: org/example/components/ComponentBase
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
    at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at org.jboss.mx.loading.RepositoryClassLoader.findClassLocally(RepositoryClassLoader.java:690)
    at org.jboss.mx.loading.RepositoryClassLoader.findClass(RepositoryClassLoader.java:670)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at org.jboss.mx.loading.RepositoryClassLoader.loadClassLocally(RepositoryClassLoader.java:200)
    at org.jboss.mx.loading.ClassLoadingTask$ThreadTask.run(ClassLoadingTask.java:131)
    at org.jboss.mx.loading.LoadMgr3.nextTask(LoadMgr3.java:399)
    at org.jboss.mx.loading.RepositoryClassLoader.loadClassImpl(RepositoryClassLoader.java:527)
    at org.jboss.mx.loading.RepositoryClassLoader.loadClass(RepositoryClassLoader.java:415)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:295)
    at java.net.FactoryURLClassLoader.loadClass(URLClassLoader.java:627)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1345)
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1204)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:169)
    ... 259 more
Caused by: java.lang.ClassNotFoundException: No ClassLoaders found for: org/example/components/ComponentBase
    at org.jboss.mx.loading.LoadMgr3.beginLoadTask(LoadMgr3.java:212)
    at org.jboss.mx.loading.RepositoryClassLoader.loadClassImpl(RepositoryClassLoader.java:521)
    at org.jboss.mx.loading.RepositoryClassLoader.loadClass(RepositoryClassLoader.java:415)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    ... 297 more

ComponentBase已加载正常,因为我可以Class.forName('org.example.components.ComponentBase')无误地执行。在初始化CustomComponent时是否使用了不同的类加载器?我尝试显式传入类加载器,但我得到了同样的错误。

1 个答案:

答案 0 :(得分:1)

由于您的插件基类位于WAR文件中(可能位于WEB-INF / classes / ...中),因此需要从custom-components.jar中删除somewhere on the classpath并将其移至'WEB-中war文件的INF / lib'文件夹。

这样两个类都由同一个类加载器加载。

执行时的方式:

Class.forName('org.example.components.ComponentBase')

它成功,因为您正在将ComponentBase加载到war类加载器中。 forName方法首先检查war的WEB-INF / lib和WEB-INF / classes并找到它。

但是当你执行

Class.forName("org.example.components.custom.CustomComponent");

war类加载器首先像以前一样检查WEB-INF / lib,WEB-INF / classes,但是在那里找不到它。然后它开始询问“父”类加载器。父类加载器(可能是JBoss特定的类加载器,或引导类加载器?)查找类并尝试加载它。

一切顺利,直到它检测到CustomComponent扩展BaseComponent。因此父类加载器尝试加载BaseComponent。但是,父类加载器无法找到BaseComponent,因为不允许查看war文件。你war classloader是一个子类加载器,类加载器只能委托给父类加载器。

考虑一下如果你取消部署战争会发生什么,你的基础类就会消失。或者如果您使用基类部署了两场战争。根据可能消失或繁殖的基类,引导类路径上的插件类的想法是没有意义的。

现在,只需将所有代码放在WAR文件中,并使类加载逻辑尽可能简单。