6

眼前的情况并不像标题所暗示的那么简单。

通过 JWS 运行的 Java 1.6_17。

我有一个类,假设MyClass它的一个实例成员变量是来自错误的 3rd 方库的类型,在该类初始化期间,它动态尝试使用Class.forName(String). 在其中一种情况下,它碰巧动态调用: .此类Class.forName("foo/Bar")名称不遵循二进制名称的 JLS 并最终导致java.lang.NoClassDefFoundError: foo/Bar.

我们有一个自定义项ClassLoader,我添加了一个 sanitize 方法ClassLoader.findClass(String)ClassLoader.loadClass(String)解决了这个问题。

我可以这样称呼: myCustomClassLoader.findClass("foo/Bar")

然后加载类没有任何问题。但是即使我提前加载了类,我仍然会在以后得到异常。这是因为在 which 的初始化过程中MyClassBar它们代码最终会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

4

3 回答 3

4

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

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

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

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

同样提供信息的是同一个人的另一篇 JavaWorld 文章,它警告您注意 Class.forName 的注意事项。这也可能会影响您的类加载安排。

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

于 2012-12-18T04:31:17.713 回答
3

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

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

我们最终获得了 OpenJDK,因为从头开始重新编译 JDK 是一场噩梦(我们尝试过)。在让 OpenJDK 与我们的产品一起工作并通过调试之后JNLPClassLoader- 事实证明,它仍在使用几个月前的一个非常旧的 .jnlp,它的资源路径错误,因此找不到该类。

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

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

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

真的很遗憾,这是问题所在。希望这可以节省其他人无数小时的挫败感,就像它给我们造成的那样。

于 2013-06-13T17:35:49.923 回答
2

如果您在修补 s 本身时没有想法ClassLoader,您可能会考虑重写库字节码本身 - 只需将“foo/bar”常量替换为正确的值,然后您根本不需要自定义进一步的类加载!

您可以在运行时或事先执行此操作。

于 2012-12-18T05:25:03.597 回答