【问题标题】:Does URLClassLoader traverse MANIFEST.MF Class-Path headers properly?URLClassLoader 是否正确遍历 MANIFEST.MF Class-Path 标头?
【发布时间】:2016-10-06 21:41:00
【问题描述】:

更新 1:确实,URL 格式差异会导致错误。这是一个显示问题的单元测试(手动剪切和混淆;希望我没有遗漏任何内容):

@Test
public void wheresWaldo2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, IOException, NoSuchMethodException {
  // Find Waldo from file:/someLocation/waldo.jar.  Prove that works.
  URL waldosJar = new File("/someLocation/waldo.jar").toURI().toURL();
  assertNotNull(waldosJar);
  assertEquals("file", waldosJar.getProtocol());
  String waldosPath = waldosJar.getPath();
  assertNotNull(waldosPath);
  assertTrue(waldosPath.endsWith("/waldo.jar"));

  ClassLoader cl = new URLClassLoader(new URL[] { waldosJar }, this.getClass().getClassLoader());

  Class<?> waldosClass = cl.loadClass("com.foobar.Waldo");
  assertNotNull(waldosClass);
  assertEquals("com.foobar.Waldo", waldosClass.getName());
  assertSame(cl, waldosClass.getClassLoader());

  Class<?> jimbosClass = cl.loadClass("com.foobar.Jimbo"); // Note: works
  assertNotNull(jimbosClass);

  // Find Waldo from jar:file:/someLocation/waldo.jar!/.  Prove that works.
  // This URL, when passed to a URLClassLoader, should result in the same
  // functionality as the first one.  But it doesn't.
  waldosJar = new URL("jar:" + waldosJar.toExternalForm() + "!/");
  assertEquals("jar", waldosJar.getProtocol());
  assertEquals("file:" + waldosPath + "!/", waldosJar.getPath());

  cl = new URLClassLoader(new URL[] { waldosJar }, this.getClass().getClassLoader());

  waldosClass = cl.loadClass("com.foobar.Waldo");
  assertNotNull(waldosClass);
  assertEquals("com.foobar.Waldo", waldosClass.getName());
  assertSame(cl, waldosClass.getClassLoader());

  jimbosClass = cl.loadClass("com.foobar.Jimbo"); // XXX FAILS
}

更新 0: 问题可能与引用 jar 文件的两个 URLs 之间假定的但不是实际的等价有关。例如,以下两个 URL 应该引用同一个文件:

  • file:/myjar.jar
  • jar:file:/myjar.jar!/

当我将使用第一种格式构建的 URL 传递给我的机器时,我认为一切正常。当我传递从第二种格式构建的 URL 时,我得到如下所述的结果。我正在测试更多以毫无疑问地确认这一切。

原始问题

(我知道this question。)

我有一个 jar 文件 waldo.jar,其中包含一个 META-INF/MANIFEST.MF,如下所示:

Manifest-Version: 1.0
Class-Path: jimbo.jar

它的以下位置也有一个类:

com/foobar/Waldo.class

该类的源代码本质上是:

package com.foobar;

public class Waldo {
  public Jimbo getJimbo() {
    return null;
  }
}

接下来,在同一个目录中,我有一个 jar 文件 jimbo.jar,其中包含一个位于以下位置的类:

com/foobar/Jimbo.class

那个类的源代码本质上是:

package com.foobar;

public class Jimbo {

}

现在我构造了一个URLClassLoader,其URL 指向waldo.jar。回顾:jimbo.jar 包含 Jimbo 并且在 waldo.jar 的“旁边”,并且适当地列在 waldo.jar 的 META-INF/MANIFEST-MF 的 Class-Path 标头中。 waldo.jar 包含 Waldo,它具有对 Jimbo 的代码引用。到目前为止和我在一起?

我可以加载com.foobar.Waldo 就好了。但是,如果我对Waldo 执行涉及com.foobar.Jimbo 的操作,例如调用Waldo.class.getDeclaredMethod("getJimbo"),我会得到NoClassDefFoundError。这是一个示例堆栈:

java.lang.NoClassDefFoundError: com/foobar/Jimbo
    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
    at java.lang.Class.getDeclaredMethod(Class.java:2128)
    at com.foobar.TestClassLoadingProblems.wheresWaldo(TestClassLoadingProblems.java:115)

    Caused by:
    java.lang.ClassNotFoundException: com.foobar.Jimbo
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at com.foobar.MyClassLoader.findClass(...) // calls super.findClass()
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)

这向我表明URLClassLoader 在所有情况下都没有正确咨询Class-Path 标头(我知道how it works in general)。

谁能解释这里发生了什么?

【问题讨论】:

  • 如果你只是尝试new Jimbo()而不是Waldo.class.getDeclaredMethod("getJimbo"),结果是什么?
  • 我当然做不到new Jimbo(),因为使用类加载器的类不知道Jimbo。 jimbosClass.newInstance() 几乎肯定会失败。我会重新调整我的测试来检查它。不过,请继续关注;问题似乎与提供给URLClassLoader 的 URL 格式有关:如果我使用 file:/myjar.jar 而不是 jar:file:/myjar.jar!/(它们应该是等效的),一切似乎都可以工作。
  • 好吧我觉得这个jar是手动生成的。您缺少包含该类的文件夹的条目。只需在其中的 jar 中添加条目(文件结构的索引类型)即可。
  • 嗨,不,这当然不是问题。不过,感谢您的评论。

标签: java urlclassloader


【解决方案1】:

这是由于bug in the JDK。

【讨论】:

    猜你喜欢
    • 2012-11-12
    • 2012-12-04
    • 2010-10-28
    • 2013-10-26
    • 2013-02-15
    • 2013-03-28
    • 1970-01-01
    • 2019-01-24
    相关资源
    最近更新 更多