从类路径目录中获取资源列表

Posted

技术标签:

【中文标题】从类路径目录中获取资源列表【英文标题】:Get a list of resources from classpath directory 【发布时间】:2011-04-24 19:12:46 【问题描述】:

我正在寻找一种从给定类路径目录中获取所有资源名称列表的方法,例如方法 List<String> getResourceNames (String directoryName)

例如,给定一个类路径目录x/y/z 包含文件a.htmlb.htmlc.html 和一个子目录dgetResourceNames("x/y/z") 应该返回一个包含以下字符串的List<String>['a.html', 'b.html', 'c.html', 'd'] .

它应该适用于文件系统和 jar 中的资源。

我知道我可以用Files、JarFiles 和URLs 编写一个快速的sn-p,但我不想重新发明***。我的问题是,鉴于现有的公开可用的库,实现getResourceNames 的最快方法是什么? Spring 和 Apache Commons 堆栈都是可行的。

【问题讨论】:

相关答案:***.com/a/30149061/4102160 勾选这个项目,解决资源文件夹扫描:github.com/fraballi/resources-folder-scanner 【参考方案1】:

这里是代码来源:forums.devx.com/showthread.php?t=153784

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

/**
 * list resources available from the classpath @ *
 */
public class ResourceList

    /**
     * for all elements of java.class.path get a Collection of resources Pattern
     * pattern = Pattern.compile(".*"); gets all resources
     * 
     * @param pattern
     *            the pattern to match
     * @return the resources in the order they are found
     */
    public static Collection<String> getResources(
        final Pattern pattern)
        final ArrayList<String> retval = new ArrayList<String>();
        final String classPath = System.getProperty("java.class.path", ".");
        final String[] classPathElements = classPath.split(System.getProperty("path.separator"));
        for(final String element : classPathElements)
            retval.addAll(getResources(element, pattern));
        
        return retval;
    

    private static Collection<String> getResources(
        final String element,
        final Pattern pattern)
        final ArrayList<String> retval = new ArrayList<String>();
        final File file = new File(element);
        if(file.isDirectory())
            retval.addAll(getResourcesFromDirectory(file, pattern));
         else
            retval.addAll(getResourcesFromJarFile(file, pattern));
        
        return retval;
    

    private static Collection<String> getResourcesFromJarFile(
        final File file,
        final Pattern pattern)
        final ArrayList<String> retval = new ArrayList<String>();
        ZipFile zf;
        try
            zf = new ZipFile(file);
         catch(final ZipException e)
            throw new Error(e);
         catch(final IOException e)
            throw new Error(e);
        
        final Enumeration e = zf.entries();
        while(e.hasMoreElements())
            final ZipEntry ze = (ZipEntry) e.nextElement();
            final String fileName = ze.getName();
            final boolean accept = pattern.matcher(fileName).matches();
            if(accept)
                retval.add(fileName);
            
        
        try
            zf.close();
         catch(final IOException e1)
            throw new Error(e1);
        
        return retval;
    

    private static Collection<String> getResourcesFromDirectory(
        final File directory,
        final Pattern pattern)
        final ArrayList<String> retval = new ArrayList<String>();
        final File[] fileList = directory.listFiles();
        for(final File file : fileList)
            if(file.isDirectory())
                retval.addAll(getResourcesFromDirectory(file, pattern));
             else
                try
                    final String fileName = file.getCanonicalPath();
                    final boolean accept = pattern.matcher(fileName).matches();
                    if(accept)
                        retval.add(fileName);
                    
                 catch(final IOException e)
                    throw new Error(e);
                
            
        
        return retval;
    

    /**
     * list the resources that match args[0]
     * 
     * @param args
     *            args[0] is the pattern to match, or list all resources if
     *            there are no args
     */
    public static void main(final String[] args)
        Pattern pattern;
        if(args.length < 1)
            pattern = Pattern.compile(".*");
         else
            pattern = Pattern.compile(args[0]);
        
        final Collection<String> list = ResourceList.getResources(pattern);
        for(final String name : list)
            System.out.println(name);
        
    
  

如果您使用的是 Spring,请查看 PathMatchingResourcePatternResolver

【讨论】:

提问者知道如何使用标准 java 类来实现它,但他想知道如何利用现有的(Spring、Apache Commons)库。 @Jeroen Rosenberg 还有另一种方式,最终被选中:) 我发现这个解决方案对于你不使用 Spring 的情况很有用——仅仅因为这个特性而添加对 Spring 的依赖是很荒谬的。所以谢谢! “肮脏”但有用:)有人可能想在getResources 方法中使用File.pathSeparator 而不是硬编码的: 代码示例取决于系统,请改用:final String[] classPathElements = classPath.split(System.pathSeparator); 警告:java.class.path 系统属性可能不包含您正在运行的实际类路径。您也可以查看类加载器并询问它从哪些 URL 加载类。当然,另一个需要注意的是,如果您在 SecurityManager 下执行此操作,则 ZipFile 构造函数可能会拒绝您对文件的访问,因此您应该抓住它。【参考方案2】:

自定义扫描仪

实现您自己的扫描仪。例如:

(limitations of this solution are mentioned in the comments)

private List<String> getResourceFiles(String path) throws IOException 
    List<String> filenames = new ArrayList<>();

    try (
            InputStream in = getResourceAsStream(path);
            BufferedReader br = new BufferedReader(new InputStreamReader(in))) 
        String resource;

        while ((resource = br.readLine()) != null) 
            filenames.add(resource);
        
    

    return filenames;


private InputStream getResourceAsStream(String resource) 
    final InputStream in
            = getContextClassLoader().getResourceAsStream(resource);

    return in == null ? getClass().getResourceAsStream(resource) : in;


private ClassLoader getContextClassLoader() 
    return Thread.currentThread().getContextClassLoader();

Spring 框架

使用 Spring Framework 中的 PathMatchingResourcePatternResolver

Ronmamo 倒影

对于巨大的 CLASSPATH 值,其他技术在运行时可能会很慢。更快的解决方案是使用 ronmamo 的 Reflections API,它会在编译时预编译搜索。

【讨论】:

如果使用反射,你真正需要的就是:new Reflections("my.package", new ResourcesScanner()).getResources(pattern) 第一个解决方案从 jar 文件执行时是否有效?对我来说 - 它没有。我可以通过这种方式读取文件,但无法读取目录。 这个答案中描述的第一种方法——将路径作为资源读取,当资源与可执行代码位于同一 JAR 中时,至少在 OpenJDK 1.8 中似乎不起作用。失败相当奇怪——从 JVM 文件处理逻辑的深处抛出 NullPointerException。就好像设计者并没有真正预料到这种资源的使用,只有部分实现。即使它适用于某些 JDK 或环境,这似乎也没有记录在案的行为。 你不能像这样读取目录,因为除了文件流之外没有目录。这个答案错了​​一半。 第一种方法似乎不起作用——至少在从开发环境运行时,在破坏资源之前。使用目录的相对路径调用 getResourceAsStream() 会导致 FileInputStream(File),如果文件是目录,则定义为抛出 FileNotFoundException。【参考方案3】:

如果您使用 apache commonsIO,您可以将其用于文件系统(可选扩展过滤器):

Collection<File> files = FileUtils.listFiles(new File("directory/"), null, false);

对于资源/类路径:

List<String> files = IOUtils.readLines(MyClass.class.getClassLoader().getResourceAsStream("directory/"), Charsets.UTF_8);

如果您不知道“directoy/”是在文件系统中还是在资源中,您可以添加一个

if (new File("directory/").isDirectory())

if (MyClass.class.getClassLoader().getResource("directory/") != null)

在调用之前并结合使用两者......

【讨论】:

当然,资源可能并不总是文件,但问题是关于源自文件/目录的资源。因此,如果您想检查不同的位置,请使用示例代码,例如如果您的资源中有用于某些默认配置的 config.xml,并且应该可以加载外部 config.xml(如果存在)... 为什么资源不是文件?资源是归档到 zip 存档中的文件。这些被加载到内存中并以特定方式访问,但我不明白为什么它们不是文件。任何不是“存档中的文件”的资源示例? 当目标资源是驻留在 JAR 文件中的目录(即 JAR 包含主应用程序和目标目录)时不起作用(NullPointerException)【参考方案4】:

所以就 PathMatchingResourcePatternResolver 而言,这就是代码中所需要的:

@Autowired
ResourcePatternResolver resourceResolver;

public void getResources() 
  resourceResolver.getResources("classpath:config/*.xml");

【讨论】:

如果您想在所有需要使用的类路径中搜索所有可能的资源classpath*:config/*.xml,这只是一个小补充【参考方案5】:

结合了 Rob 的回答。

final String resourceDir = "resourceDirectory/";
List<String> files = IOUtils.readLines(Thread.currentThread().getClass().getClassLoader().getResourceAsStream(resourceDir), Charsets.UTF_8);

for (String f : files) 
  String data = IOUtils.toString(Thread.currentThread().getClass().getClassLoader().getResourceAsStream(resourceDir + f));
  // ... process data

【讨论】:

如何获取所有资源:即从/' or .`或./开始,这些资源都没有实际工作【参考方案6】:

使用Reflections

获取类路径中的所有内容:

Reflections reflections = new Reflections(null, new ResourcesScanner());
Set<String> resourceList = reflections.getResources(x -> true);

另一个例子 - 从 some.package 中获取所有扩展名为 .csv 的文件:

Reflections reflections = new Reflections("some.package", new ResourcesScanner());
Set<String> resourceList = reflections.getResources(Pattern.compile(".*\\.csv"));

【讨论】:

有什么方法可以在不导入谷歌依赖项的情况下达到同样的效果?我想避免仅仅为了执行这个任务而产生这种依赖。 Reflections 不是 Google 库。 也许是过去。我的回答来自 2015 年,我确实在 2015 年之前使用短语“Google Reflections”找到了对该库的一些引用。我看到代码已经移动了一点,并且已经从 code.google.com here 移动到 github here 它不起作用。您需要添加更好的示例。 org.reflections.ReflectionsException 失败:没有为我配置 Scanner ResourcesScanner。【参考方案7】:

根据上面@rob 的信息,我创建了我要发布到公共领域的实现:

private static List<String> getClasspathEntriesByPath(String path) throws IOException 
    InputStream is = Main.class.getClassLoader().getResourceAsStream(path);

    StringBuilder sb = new StringBuilder();
    while (is.available()>0) 
        byte[] buffer = new byte[1024];
        sb.append(new String(buffer, Charset.defaultCharset()));
    

    return Arrays
            .asList(sb.toString().split("\n"))          // Convert StringBuilder to individual lines
            .stream()                                   // Stream the list
            .filter(line -> line.trim().length()>0)     // Filter out empty lines
            .collect(Collectors.toList());              // Collect remaining lines into a List again

虽然我没想到getResourcesAsStream 会在目录上这样工作,但它确实如此,而且效果很好。

【讨论】:

【参考方案8】:

Spring frameworkPathMatchingResourcePatternResolver 在这些方面真的很棒:

private Resource[] getXMLResources() throws IOException

    ClassLoader classLoader = MethodHandles.lookup().getClass().getClassLoader();
    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader);

    return resolver.getResources("classpath:x/y/z/*.xml");

Maven 依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>LATEST</version>
</dependency>

【讨论】:

【参考方案9】:

这应该有效(如果弹簧不是一个选项):

public static List<String> getFilenamesForDirnameFromCP(String directoryName) throws URISyntaxException, UnsupportedEncodingException, IOException 
    List<String> filenames = new ArrayList<>();

    URL url = Thread.currentThread().getContextClassLoader().getResource(directoryName);
    if (url != null) 
        if (url.getProtocol().equals("file")) 
            File file = Paths.get(url.toURI()).toFile();
            if (file != null) 
                File[] files = file.listFiles();
                if (files != null) 
                    for (File filename : files) 
                        filenames.add(filename.toString());
                    
                
            
         else if (url.getProtocol().equals("jar")) 
            String dirname = directoryName + "/";
            String path = url.getPath();
            String jarPath = path.substring(5, path.indexOf("!"));
            try (JarFile jar = new JarFile(URLDecoder.decode(jarPath, StandardCharsets.UTF_8.name()))) 
                Enumeration<JarEntry> entries = jar.entries();
                while (entries.hasMoreElements()) 
                    JarEntry entry = entries.nextElement();
                    String name = entry.getName();
                    if (name.startsWith(dirname) && !dirname.equals(name)) 
                        URL resource = Thread.currentThread().getContextClassLoader().getResource(name);
                        filenames.add(resource.toString());
                    
                
            
        
    
    return filenames;

【讨论】:

最近收到了反对票 - 如果您发现问题,请分享,谢谢! 谢谢@ArnoUnkrig - 如果您愿意,请分享您的更新解决方案【参考方案10】:

有了 Spring,这很容易。无论是一个文件,还是文件夹,甚至是多个文件,都有机会,你可以通过注入来做到。

此示例演示了注入位于x/y/z 文件夹中的多个文件。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

@Service
public class ***Service 
    @Value("classpath:x/y/z/*")
    private Resource[] resources;

    public List<String> getResourceNames() 
        return Arrays.stream(resources)
                .map(Resource::getFilename)
                .collect(Collectors.toList());
    

它确实适用于文件系统和 JAR 中的资源。

【讨论】:

我想获取 /BOOT-INF/classes/static/stories 下的文件夹名称。我用 @Value("classpath:static/stories/*") 尝试你的代码,它在运行 JAR 时返回一个空列表。它适用于类路径中的资源,但不适用于 JAR 中的资源。 @Value("classpath:x/y/z/*") 私有资源[] 资源;对我做了伎俩。有搜索时间!谢谢!【参考方案11】:

我认为您可以利用 [Zip 文件系统提供程序][1] 来实现这一点。使用FileSystems.newFileSystem 时,您似乎可以将该 ZIP 中的对象视为“常规”文件。

在上面的链接文档中:

在传递给FileSystems.newFileSystem 方法的java.util.Map 对象中指定zip 文件系统的配置选项。有关 zip 文件系统的提供程序特定配置属性的信息,请参阅 [Zip 文件系统属性][2] 主题。

一旦你有了一个 zip 文件系统的实例,你就可以调用 [java.nio.file.FileSystem][3] 和 [java.nio.file.Path][4] 类的方法来执行复制、移动和重命名等操作文件,以及修改文件属性。

[Java 11 states][5] 中jdk.zipfs 模块的文档:

zip 文件系统提供程序将 zip 或 JAR 文件视为文件系统,并提供操作文件内容的能力。如果安装了 zip 文件系统提供程序,则可以由 [FileSystems.newFileSystem][6] 创建。

这是我使用您的示例资源所做的一个人为的示例。请注意,.zip.jar,但您可以调整代码以使用类路径资源:

设置

cd /tmp
mkdir -p x/y/z
touch x/y/z/a,b,c.html
echo 'hello world' > x/y/z/d
zip -r example.zip x

Java

import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.util.Collections;
import java.util.stream.Collectors;

public class MkobitZipRead 

  public static void main(String[] args) throws IOException 
    final URI uri = URI.create("jar:file:/tmp/example.zip");
    try (
        final FileSystem zipfs = FileSystems.newFileSystem(uri, Collections.emptyMap());
    ) 
      Files.walk(zipfs.getPath("/")).forEach(path -> System.out.println("Files in zip:" + path));
      System.out.println("-----");
      final String manifest = Files.readAllLines(
          zipfs.getPath("x", "y", "z").resolve("d")
      ).stream().collect(Collectors.joining(System.lineSeparator()));
      System.out.println(manifest);
    
  


输出

Files in zip:/
Files in zip:/x/
Files in zip:/x/y/
Files in zip:/x/y/z/
Files in zip:/x/y/z/c.html
Files in zip:/x/y/z/b.html
Files in zip:/x/y/z/a.html
Files in zip:/x/y/z/d
-----
hello world

【讨论】:

【参考方案12】:

即使我将资源放在资源文件夹中并遵循上述答案,这两个答案都不适合我。真正的诡计是:

@Value("file:*/**/resources/**/schema/*.json")
private Resource[] resources;

【讨论】:

【参考方案13】:

目前列出类路径中所有资源的最强大的机制是to use this pattern with ClassGraph,因为它处理widest possible array of classpath specification mechanisms,包括新的JPMS 模块系统。 (我是ClassGraph的作者。)

List<String> resourceNames;
try (ScanResult scanResult = new ClassGraph().whitelistPaths("x/y/z").scan()) 
    resourceNames = scanResult.getAllResources().getNames();

【讨论】:

具有一致行为的优秀库。【参考方案14】:

我的方式,没有 Spring,在单元测试中使用:

URI uri = TestClass.class.getResource("/resources").toURI();
Path myPath = Paths.get(uri);
Stream<Path> walk = Files.walk(myPath, 1);
for (Iterator<Path> it = walk.iterator(); it.hasNext(); ) 
    Path filename = it.next();   
    System.out.println(filename);

【讨论】:

不起作用,当您运行 jar 时:java.nio.file.FileSystemNotFoundException at Paths.get(uri)

以上是关于从类路径目录中获取资源列表的主要内容,如果未能解决你的问题,请参考以下文章

从类路径资源(XML 文件)获取输入流

java获取指定目录中的文件列表

从类路径加载资源

如何使用 informix.jvp.dbapplet.impl.JVPClassLoader 从类路径加载资源?

从类路径资源解析 XML 文档时出现意外异常

C# 从类库中获取资源图片,把图片资源保存到类库中