JAR 索引和 getResources

JAR indexing and getResources

在我看来 JAR file indexing breaks the mechanics of ClassLoader.getResources()。考虑以下程序:

import java.io.*;
import java.net.*;
import java.util.*;

public class TryIt {
    public static void main(String[] args) throws Exception {
        URL[] urls = {
            (new File("a.jar")).getAbsoluteFile().toURI().toURL(),
            (new File("b.jar")).getAbsoluteFile().toURI().toURL()
        };
        URLClassLoader cl = URLClassLoader.newInstance(urls);
        String[] res = { "foo", "foo/", "foo/arb", "foo/bar", "foo/cab" };
        for (String r: res) {
            System.out.println("'" + r + "':");
            for (URL u: Collections.list(cl.getResources(r)))
                System.out.println(" " + u);
        }
    }
}

现在准备该程序中提到的 JAR 文件:

mkdir a/foo b/foo
touch a/foo/arb a/foo/bar b/foo/bar b/foo/cab
echo "Class-Path: b.jar" > mf
jar cfm a.jar mf -C a foo
jar cf b.jar -C b foo

如果你运行 java TryIt,你会得到这样的输出:

'foo':
 jar:file:…/a.jar!/foo
 jar:file:…/b.jar!/foo
'foo/':
 jar:file:…/a.jar!/foo/
 jar:file:…/b.jar!/foo/
'foo/arb':
 jar:file:…/a.jar!/foo/arb
'foo/bar':
 jar:file:…/a.jar!/foo/bar
 jar:file:…/b.jar!/foo/bar
'foo/cab':
 jar:file:…/b.jar!/foo/cab

但是如果你 运行 jar -i a.jar 创建一个索引,那么与上面相同的命令会打印出这个:

'foo':
 jar:file:…/a.jar!/foo
'foo/':
 jar:file:…/a.jar!/foo/
'foo/arb':
 jar:file:…/a.jar!/foo/arb
'foo/bar':
 jar:file:…/a.jar!/foo/bar
'foo/cab':
 jar:file:…/b.jar!/foo/cab

索引本身如下所示:

JarIndex-Version: 1.0

a.jar
foo

b.jar
foo

getResources 的合同不是暗示应返回 所有 匹配给定名称的可用资源吗?

Finds all the resources with the given name.

the JAR File Specification 不允许索引包跨越多个 JAR 文件吗?

Normally one package name is mapped to one jar file, but if a particular package spans more than one jar file, then the mapped value of this package will be a list of jar files.

是否有某处说明我观察到的行为确实是正确的(或至少是允许的)行为?

是否有一些变通方法来获取所有命名资源而不考虑索引?

这似乎是一个错误。
我已将它报告给 Oracle,它现在作为 bug 8150615.

在他们的错误数据库中

我在 OpenJDK 源代码中进行了一些挖掘,并在其中找到了这种行为的原因。

这里的相关 class 是 sun.misc.URLClassPath. It contains a (lazily constructed) list of loaders, and queries each loader in turn to assemble its result. However, if a JAR file contains an index, then the JAR files therein will explicitely be excluded from getting added to the list of loaders. Instead, the loader for the JAR containing the index will query said index for the name in question, and traversed the resulting list. But here is the catch: this happens in a method URLClassPath$JarLoader.getResource,其中 return 是 单个 Resource 对象。此方法无法 return 多个资源。由于索引中的所有对象都由单个加载程序处理,因此我们只得到一个资源。