如何正确使用ClassLoader.getResources()?

94

如何使用ClassLoader.getResources()从类路径递归查找资源?

例如:

  • 查找META-INF目录下的所有资源: 可以尝试以下代码:

    getClass().getClassLoader().getResources("META-INF")

    不幸的是,这只会检索到指定目录的URL

  • 递归查找所有名为bla.xml的资源

    getClass().getClassLoader().getResources("bla.xml")

    但这将返回一个空的Enumeration

作为一个奖励问题: ClassLoader.getResources()ClassLoader.getResource()有什么不同?


12
@Andrew,许多框架会根据档案中的名称或扩展名对一些文件进行迭代,以自动化某些过程,例如查找 Stripes 的 ActionBean 或 Hibernate 的 hbm.xml 文件。请问您需要具体翻译哪部分内容? - bestsss
2
请参见https://dev59.com/CHM_5IYBdhLWcg3wUxZB。 - Vadzim
它让你感到困惑的原因是getResources在类加载器上工作,该加载器可以在类路径中有多个JAR。因此,如果您有多个具有相同资源的JAR,则会获取所有这些资源。但是,它不打算搜索目录内部。使用getResources(“META-INF”)可以获取CL搜索路径中的所有META-INFO目录,如果CL是单个jar文件类加载器,则最多只能获取一个条目。 - eckes
5个回答

44

Spring框架有一个类,可以递归地搜索类路径:

PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
resolver.getResources("classpath*:some/package/name/**/*.xml");

1
如果您正在尝试解析 jar 中的资源,并且不确定模式应该是什么样子,请运行 jar tf myjar.jar。我最终使用了 PathMatchinResourcePatternResolver("definitions/**/*.json") - MatrixManAtYrService
classpath* 实际上是什么意思:它是否旨在用当前类路径的完全限定内容替换以进行搜索? - WestCoastProjects
如果有多个JAR文件包含相同的资源,则classpath:my/resource.xml应该会抛出错误,而classpath*:my/resource.xml会返回所有匹配的结果。更多细节请参见例如http://tech-tauk.blogspot.com/2010/04/difference-between-classpath-classpath.html。 - rec

25

没有办法递归地搜索类路径。要能够以这种方式检索资源,您需要知道资源的完整路径名。资源可能在文件系统中的目录中,也可能在jar文件中,因此执行“类路径”的目录列表不像简单。您将需要提供资源的完整路径,例如 '/com/mypath/bla.xml'。

对于您的第二个问题,getResource将返回与给定资源名称匹配的第一个资源。搜索类路径的顺序在getResource的javadoc中给出。


13
没办法通过类路径递归搜索... 当然,是有可能的。例如:URLClassLoader.getURLs()。打开JarFile,进行迭代搜索即可。 - bestsss
3
如果URL不指向Jar文件而是指向目录,会怎样? - MRalwasser
6
甚至更糟糕的是,如果它指向一个HTTP网址呢? - Joachim Sauer
4
类加载器可以只是java.lang.ClassLoader的简单子类,没有URL部分也没关系。它可以在内存中生成类等等。它可能有非常自定义的getResource等方法。如果它指向一些文件系统,你可以像处理它那样简单地处理它。除了applet之外,现在没有远程保存文件的情况了。URL可以是任何东西,但只要它是JAR文件,你就无法从http检索jar。--我所说的并不总是正确的,也不是通用解决方案,但它涵盖了98%以上的情况。 - bestsss

23

这是获取特定 URL 对象所指向的文件对象最简单的方法:

File file=new File(url.toURI());
现在,针对您具体的问题:
查找META-INF“目录”中的所有资源:
您确实可以获取指向此URL的File对象。
Enumeration<URL> en=getClass().getClassLoader().getResources("META-INF");
if (en.hasMoreElements()) {
    URL metaInf=en.nextElement();
    File fileMetaInf=new File(metaInf.toURI());

    File[] files=fileMetaInf.listFiles();
    //or 
    String[] filenames=fileMetaInf.list();
}
  • 递归地获取所有名为bla.xml的资源

在这种情况下,您需要编写一些自定义代码。以下是一个虚拟示例:

final List<File> foundFiles=new ArrayList<File>();

FileFilter customFilter=new FileFilter() {
    @Override
    public boolean accept(File pathname) {

        if(pathname.isDirectory()) {
            pathname.listFiles(this);
        }
        if(pathname.getName().endsWith("bla.xml")) {
            foundFiles.add(pathname);
            return true;
        }
        return false;
    }

};      
//rootFolder here represents a File Object pointing the root forlder of your search 
rootFolder.listFiles(customFilter);

运行代码后,您将在foundFiles列表中获得所有找到的出现。


11
我怀疑这种方法适用于所有类型的ClassLoader(例如,jar文件中的类)。 - MRalwasser
25
我进行了测试,结果出现了异常:java.lang.IllegalArgumentException: URI is not hierarchical. 您无法从类似于“jar:...”的不透明URI创建文件对象。 - cn1h

13

这里是基于bestsss答案的代码:

    Enumeration<URL> en = getClass().getClassLoader().getResources(
            "META-INF");
    List<String> profiles = new ArrayList<>();
    while (en.hasMoreElements()) {
        URL url = en.nextElement();
        JarURLConnection urlcon = (JarURLConnection) (url.openConnection());
        try (JarFile jar = urlcon.getJarFile();) {
            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
                String entry = entries.nextElement().getName();
                System.out.println(entry);
            }
        }
    }

2
这种结构一开始让我感到有些困惑。看起来,getClass().getClassLoader().getResources("META-INF") 的唯一目的是获取 jar 包中某个任意文件的引用,然后我们使用它来获取 JarURLConnection。我最初以为这会迭代 META-INF 目录中的文件,但实际上它会迭代整个 jar 包中的每个文件。 - Alex Pritchard
它对我没有用。它只显示了类路径中第一个jar文件J的内容,其中J包含以getResources(path)方法给定的path开头的条目。 因此,您的代码片段的结果是不完整的,并且取决于类路径中元素的顺序。 因此,当@krock说“没有递归搜索类路径的方法”时,他是正确的。 - Readren
抱歉打扰了,但这确实有效。唯一的问题是“if”语句应该改为“while”。 - Buddha Buddy
似乎无法与现代(Java 11)AppClassLoader一起使用 - 它不会迭代类路径JAR。 - alamar

5
MRalwasser,我给你一个提示,将URL.getConnection()转换为JarURLConnection。 然后使用JarURLConnection.getJarFile(),就可以获得JarFile并自由访问其中的资源。
其余的事情交给你了。
希望这能帮到你!

URL.getConnection()是什么意思?我无法找到这个方法。 - m0skit0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接