【问题标题】:Load Java class with super class from a dynamically loaded jar file从动态加载的 jar 文件中加载具有超类的 Java 类
【发布时间】:2017-02-25 09:01:54
【问题描述】:

我正在尝试从动态加载的 jar 文件中加载类。目前,我可以加载一个不扩展为根类 Object 以外的类的类,但我无法加载扩展自定义类的类。我的问题的详细描述如下。

在项目A中,有一个类A,如:

class A {
}

在项目 B 中,有一个 B1 类和一个 B2 类,例如:

class B1 {
}

class B2 extends A {
}

我将项目 A 导出到 a.jar,将项目 B 导出到 b.jar。测试代码为:

File f = new File("Z:\\Jars\\b.jar");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { f.toURL() },
        System.class.getClassLoader());

Class<?> classB1 = (Class<?>) urlClassLoader.loadClass("b.B1");
System.out.println("B1 is loaded");

Class<?> classB2 = (Class<?>) urlClassLoader.loadClass("b.B2");
System.out.println("B2 is loaded");

类B1加载成功,但是由于类a.A未找到异常导致B2类没有加载:

java -cp a.jar;b.jar; a.Main
B1 is loaded
Exception in thread "main" java.lang.NoClassDefFoundError: a/A
Caused by: java.lang.ClassNotFoundException: a.A
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 12 more

我该如何解决这个问题? 谢谢。

附言。包含 a.jar 后,可以加载 B2 类,但无法通过以下测试:

Class<A> classB2 = (Class<A>) urlClassLoader.loadClass("b.B2");
A ab2 = classB2.newInstance();

第一行没问题,但第二行触发了以下异常:

Exception in thread "main" java.lang.ClassCastException: b.B2 cannot be cast to a.A
    at a.Main.main(Main.java:22)

B2 应该是 A 实例,知道为什么会这样吗?

【问题讨论】:

  • 如何在不包含 A 类的情况下构建 jar b.jar?您是从 b.jar 中手动删除 A.class 吗?
  • 我用的是Eclipse,我把B项目设置为依赖A项目,这样B项目就可以构建,并且可以导出一个jar文件。对于上面的演示案例,我的问题通过包含 a.jar 得到了解决,可以加载 B2 类并将其强制转换为 A。
  • stackoverflow.com/questions/2591779/cast-across-classloader的第一个回答说不同类加载器加载的同一个类的类不能相互转换。
  • @DeepParticle 正确,请参阅我的回答,了解您可以做些什么。

标签: java jar jvm classloader dynamic-class-loaders


【解决方案1】:

您有两个名为 a.A 的类,一个由您的线程的默认类加载器加载,另一个由您的自定义类加载器加载。

这是消息令人困惑的地方,因为您实际上有两个打印a.A 的类,但是来自不同的类加载器您不能将一个交换为另一个。你需要做的是

Class classB2 = urlClassLoader.loadClass("b.B2")
Object ab2 = classB2.newInstance();

这是因为您的线程将加载的 a.A 来自默认类加载器,即使它恰好具有相同的名称,您也不能使用它。

另一种解决方法是让一个类加载器加载a.A

File f = new File("Z:\\Jars\\b.jar");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { f.toURL() });

这将使用ClassLoader.getSystemClassLoader() 来加载a.A,因此只有一个具有该名称的类。

【讨论】:

  • 感谢您的清晰解释。你提出的上下文类加载器对我来说是一个新概念,我看看能不能让它工作。
  • @DeepParticle 这是当前线程用来加载类的那个。
  • 只获取A的类加载器比使用上下文类加载器要好
  • 请注意此类声明。引入此功能时,上下文加载器的概念可能类似于“当前线程用于加载类的那个”,但线程不加载类。因此,在动态加载类或构建新的类加载器时使用该类加载器是一种纯粹的约定,但实际上,该上下文加载器与 JVM 无关。它默认与ClassLoader.getSystemClassLoader() 相同,这就是它在这里工作的原因。但它也可以通过完全删除 ClassLoader 参数来工作。
  • 即使用URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { f.toURL() }); 会起作用。原始代码中的 System.class.getClassLoader() 参数显式选择了应用程序类加载器,因为 System.class 由引导加载器加载。 System.class.getClassLoader() 不应与 ClassLoader.getSystemClassLoader()混淆
猜你喜欢
  • 2014-12-29
  • 1970-01-01
  • 2012-02-05
  • 2020-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-07
相关资源
最近更新 更多