【问题标题】:Loading external classes that depend on internal classes加载依赖于内部类的外部类
【发布时间】:2013-09-16 17:57:48
【问题描述】:

我正在尝试为 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 时是否使用了不同的类加载器?我尝试显式传入一个类加载器,但我得到了同样的错误。

【问题讨论】:

  • “它”是谁?你可以发布堆栈跟踪吗?还有:stackoverflow.com/questions/3957637/…
  • Class.forName 抛出异常。我已经编辑了帖子以使其更清晰。
  • ServiceLoader 类在这种情况下不会有太大用处,因为找到 CustomComponent 类名的“魔术”基于 bean 配置和与现有匹配的注释的混合工作内部会议。没有一组固定的基类可供ServiceLoader 查找。
  • custom-components.jar 在哪里?在你的战争 WEB-INF/lib 中?
  • custom-components.jar 就在类路径的某个位置。

标签: java plugins reflection classloader


【解决方案1】:

由于您的插件基类在您的 WAR 文件中(大概在 WEB-INF/classes/...中),您需要从 somewhere on the classpath 中删除 custom-components.jar 并将其移动到 'WEB-INF/lib'你的战争文件的文件夹。

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

你现在拥有它的方式,当你执行时:

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

它成功了,因为您正在将 ComponentBase 加载到您的 war 类加载器中。 forName 方法首先检查战争的 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 文件中,并使类加载逻辑尽可能简单。

【讨论】:

  • 理论上,如果我们知道 jar 文件的路径,我们可以创建一个新的 URLClassLoader,它首先从 jar 文件加载,然后将 JBoss 类加载器作为父级。 CustomComponent 应该在URLClassLoader 中找到,然后当它查找BaseComponent 时,它会在父 JBoss 类加载器中查找。但是,当我尝试它时,我仍然得到同样的错误。这如何适合您描述的模型?在尝试自行解决之前,孩子是否会查看其父母的缓存?
  • 如果 jar 不在类路径中,则上述技术有效。这表明它在尝试自己找到它之前确实会先查看它的父缓存。
猜你喜欢
  • 1970-01-01
  • 2020-01-03
  • 2016-08-30
  • 2019-06-03
  • 2013-11-05
  • 1970-01-01
  • 2012-06-11
  • 1970-01-01
  • 2016-12-17
相关资源
最近更新 更多