【问题标题】:How do I control which ClassLoader loads a class?如何控制哪个 ClassLoader 加载一个类?
【发布时间】:2012-12-05 04:50:50
【问题描述】:

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

通过 JWS 运行的 Java 1.6_17。

我有一个类,比如说MyClass,它的实例成员变量之一是来自错误的第 3 方库的类型,在该类初始化期间,它动态尝试使用 Class.forName(String) 加载它自己的一些类。在其中一种情况下,它碰巧动态调用:Class.forName("foo/Bar")。此类名称不遵循二进制名称的 JLS,最终导致 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:帮助我强制所有 MyClass 引用的第三方库中的 Class.forName(String) 调用 - 使用我选择的类加载器。

【问题讨论】:

  • “来自错误的 3rd 方库” 最终最好的策略是替换该 API。
  • @AndrewThompson 可能会这样。我希望它不会。因此,如果有人有任何想法 - 我全神贯注!
  • 我做了类似的事情,创建了一个引导类并使用 URLClassLoader 从那里加载我们所有的外部库。当 boostrap 类启动时,它们不在类路径中,因此他们看到的唯一类加载器是自定义类加载器。不是答案,因为我不知道它是否适用于 JWS。我们这样做是为了在运行时加载插件库。

标签: java classloader jnlp java-web-start dynamic-class-loaders


【解决方案1】:

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

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

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

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

同样信息丰富的是同一个人的this other JavaWorld article,它会警告您有关 Class.forName 的警告。这也可能会影响您的类加载安排。

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

【讨论】:

  • +1 极好的信息。我明天会消化这个。如果这成功了,我会把它标记为答案。谢谢!
  • 您的研究是从哪里获得的?有休息吗?
【解决方案2】:

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

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

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

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

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

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

真的很遗憾这是问题所在。希望这可以避免其他人像它给我们带来的无数小时的挫败感。

【讨论】:

    【解决方案3】:

    如果您对自己修补ClassLoaders 的想法不够用,您可能会考虑重写库字节码本身——只需将“foo/bar”常量替换为正确的值,然后您就不需要完全自定义进一步的类加载!

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

    【讨论】:

    • +1 好主意。然而,正如我在我的问题中指出的那样(并试图澄清但失败了 - 抱歉!)它正在执行动态类加载。我已经获取了源代码,并为“foo/bar”进行了搜索,所有出现的都是导入(以及类 def 本身)。所以类名不是静态的。这个问题大约在一年前发生在这个确切的库中。它正在使用我们的自定义类加载器,在通过它进行调试后,我看到它正在使用动态字符串调用Class.forName(),因此我修改了我们的自定义类加载器以清理类名并解决了问题,直到发生这种情况。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-23
    • 1970-01-01
    • 2018-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多