如何控制哪个ClassLoader加载一个类?

Har*_*n F 6 java jnlp classloader java-web-start dynamic-class-loaders

手头的情况并不像标题似乎表明的那么简单.

Java 1.6_17通过JWS运行.

我有一个类,让我们说MyClass,它的一个实例成员变量是来自错误的第三方库的Type,在类初始化期间它动态地尝试加载它自己的一些类Class.forName(String).在其中一种情况下,它恰好动态调用:Class.forName("foo/Bar").这个类名不遵循JLS的二进制名称,最终导致a java.lang.NoClassDefFoundError: foo/Bar.

我们有一个自定义ClassLoader,我已经添加了一个清理方法,ClassLoader.findClass(String)ClassLoader.loadClass(String)修复了这个问题.

我可以称之为: myCustomClassLoader.findClass("foo/Bar")

然后加载类没有任何问题.但即使我提前加载课程,我仍然会在以后获得异常.这是因为在初始化期间MyClass引用Bar- 他们的代码最终Class.forName("foo/Bar")在某处静态块中调用.如果它试图使用的ClassLoader是我的自定义类加载器,那么这实际上是可以的.但事实并非如此.这是com.sun.jnlp.JNLPClassLoader不做这样的卫生,因此我的问题.

我已经确定将Thread.currentThread().getContextClassLoader()其设置为我的自定义类加载器.但是这(如你所知)没有效果.main()由于我阅读的一些东西,我甚至把它设置为我做的第一件事,MyClass.class.getClassLoader()- 是JNLPClassLoader.如果我可以强迫它不是JNLPClassLoader而是使用我的,那么问题就解决了.

如何通过在类初始化期间进行的静态Class.forName("foo/Bar")调用来控制使用哪个ClassLoader加载类?我相信如果我可以强制MyClass.class.getClassLoader()返回我的自定义类加载器,我的问题将得到解决.

如果有人有想法,我愿意接受其他选择.

TL; DR:帮助我强制Class.forName(String)所引用的第三方库中的所有呼叫MyClass- 使用我选择的类加载器.

Mih*_*ila 5

这让我想起10年前读过的一篇关于Java中类加载安排的文章。它仍然存在于 JavaWorld 上

本文不会直接回答您的问题,但可能有助于理解您的问题。您需要MyClass通过自定义类加载器进行加载,并胜过默认的类加载行为,即首先将类加载委托给父类加载器,并且仅在失败时才尝试加载类。

允许MyClass由您以外的类加载器加载将存储从实例化类到该类加载器的关系(通过getClassLoader),并导致 Java 使用其他类加载器来尝试发现在编译时找到的任何引用的类,从而有效地绕过您的自定义类加载器。凭借类加载器层次结构和委托模型。如果MyClass是由您的类加载器定义的,您将获得第二次机会。

这听起来像是一项类似的工作URLClassLoader,覆盖loadClass并胜过驻留在 JAR 中的类的委托模型。您可能需要使用引导方法(如 Thomas 在上面的评论中建议的那样)来强制通过自定义类加载器加载单个入口点类,并拖动所有其他入口点类。

同一个人撰写的另一篇 JavaWorld 文章也提供了丰富的信息,其中警告您有关 Class.forName 的注意事项。这也可能会影响你的类加载安排。

我希望这能有所帮助并提供有用的信息。无论如何,这听起来像是一个困难的解决方案,随着代码的发展很容易被破坏。


Har*_*n F 3

我认为每个人都为回答这个问题做出了很好的尝试。然而事实证明我误判了这个问题。

我让一位同事接管了这个问题,并要求他获取一个带有调试标志的 JDK,这样我们就可以调试它JNLPClassLoader以查看发生了什么,因为我已经尝试了这里的所有建议以及一些建议。

我们最终得到了 OpenJDK,因为从头开始重新编译 JDK 简直就是一场噩梦(我们尝试过)。在让 OpenJDK 使用我们的产品并进行调试后,JNLPClassLoader结果发现它仍然使用几个月前的非常旧的 .jnlp,该文件的资源路径错误,因此无法找到该类。

我们很困惑为什么它仍然使用古老的 .jnlp,即使我们已经使用正确的 .jnlp 正确地重新部署了服务器多次,并且它们之间的大量代码更改在运行时反映在我们的客户端应用程序中。

事实证明,在客户端计算机上,Java 缓存了 .jnlp 文件。即使您的应用程序发生更改并重新下载您的应用程序,但无论出于何种原因,它仍然不会重新下载新的 .jnlp。因此它将使用所有新代码,但使用缓存的 .jnlp 查找资源/类路径。

如果您运行: javaws -uninstall 在客户端计算机上,那么这将清除 .jnlp 缓存,并且下次它将使用正确的 .jnlp 文件。

真的很难过这就是问题所在。希望这可以避免其他人像我们一样遭受无尽的挫败感。