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

时间:2012-12-18 01:58:23

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

手头的情况并不像标题所示那么简单。

通过JWS运行的Java 1.6_17。

我有一个类,让我们说MyClass,其中一个实例成员变量是来自错误的第三方库的Type,在类初始化期间,它动态地尝试使用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:帮助我强制Class.forName(String)引用的第三方库中的所有MyClass次呼叫 - 使用我选择的类加载器。

3 个答案:

答案 0 :(得分:4)

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

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

允许MyClass由除您之外的类加载器加载将存储从实例化类到该类加载器的关系(通过getClassLoader)并使Java使用该其他类加载器尝试发现任何引用的类found at compile time,通过类加载器层次结构和委托模型有效地绕过了自定义类加载器。如果您的类加载器MyClass改为定义,那么您将获得第二次机会。

对于类似URLClassLoader的内容来说,这听起来像是一项工作,会覆盖loadClass,并且会占用JAR中的类的委托模型。您可能希望使用引导方法(如Thomas在上面的注释中所建议的)强制通过自定义类加载器加载单个入口点类,并使用它拖动所有其他类。

同一个人提供的信息也是this other JavaWorld article,它会警告你关于Class.forName的警告。这也可能会导致您的分类加载安排。

我希望这会有所帮助,并证明其内容丰富。无论如何,这听起来像是一个难以解决的问题,随着代码的发展很容易破解。

答案 1 :(得分:2)

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

您可以在运行时或事先做到这一点。

答案 2 :(得分:2)

我认为每个人都很好地回答了这个问题。然而,事实证明我误解了这个问题。

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

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

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

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

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

真的很难过,这就是问题所在。希望这可以为其他人带来无尽的挫折感,就像它给我们带来的那样。