Lấy danh sách các tài nguyên từ thư mục classpath


247

Tôi đang tìm cách để có được một danh sách tất cả các tên tài nguyên từ một thư mục classpath đã cho, giống như một phương thức List<String> getResourceNames (String directoryName).

Ví dụ, với một thư mục classpath x/y/zchứa file a.html, b.html, c.htmlvà một thư mục con d, getResourceNames("x/y/z")phải trả lại một List<String>chứa chuỗi kí tự sau: ['a.html', 'b.html', 'c.html', 'd'].

Nó nên hoạt động cả cho các tài nguyên trong hệ thống tập tin và lọ.

Tôi biết rằng tôi có thể viết một đoạn nhanh với Files, JarFiles và URLs, nhưng tôi không muốn phát minh lại bánh xe. Câu hỏi của tôi là, với các thư viện có sẵn công khai, cách nhanh nhất để thực hiện là getResourceNamesgì? Các ngăn xếp Spring và Apache Commons đều khả thi.


1
Câu trả lời liên quan: stackoverflow.com/a/30149061/4102160
Cfx

Kiểm tra dự án này, giải quyết việc quét thư mục tài nguyên: github.com/fraballi/resource-folder-scanner
Felix Aballi

Câu trả lời:


165

Máy quét tùy chỉnh

Thực hiện máy quét của riêng bạn. Ví dụ:

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();
}

Khung mùa xuân

Sử dụng PathMatchingResourcePatternResolvertừ Spring Framework.

Phản xạ Ronmamo

Các kỹ thuật khác có thể chậm trong thời gian chạy đối với các giá trị CLASSPATH lớn. Một giải pháp nhanh hơn là sử dụng API Reflections của ronmamo , dịch vụ này sẽ biên dịch trước tìm kiếm tại thời điểm biên dịch.


12
nếu sử dụng Reflections, tất cả những gì bạn thực sự cần:new Reflections("my.package", new ResourcesScanner()).getResources(pattern)
zapp

27
Liệu giải pháp đầu tiên hoạt động khi nó thực hiện từ tệp jar? Đối với tôi - nó không. Tôi có thể đọc tệp bằng cách này, nhưng không thể đọc thư mục.
Valentina Chumak

5
Phương thức đầu tiên được mô tả trong câu trả lời này - đọc đường dẫn dưới dạng tài nguyên, dường như không hoạt động khi các tài nguyên nằm trong cùng JAR với mã thực thi, ít nhất là với OpenJDK 1.8. Thất bại khá kỳ lạ - một NullPulumException được ném từ sâu trong logic xử lý tệp của JVM. Như thể các nhà thiết kế đã không thực sự lường trước việc sử dụng tài nguyên này và chỉ có một phần thực hiện. Ngay cả khi nó hoạt động trên một số JDK hoặc môi trường, điều này dường như không phải là hành vi được ghi lại.
Kevin Boone

7
Bạn không thể đọc thư mục như thế này, vì không có thư mục nào ngoài luồng tệp. Câu trả lời này là một nửa sai.
Thomas Decaux

4
Phương thức đầu tiên dường như không hoạt động - ít nhất là khi chạy từ môi trường phát triển, trước khi làm cạn kiệt tài nguyên. Cuộc gọi đến getResourceAsStream()với một đường dẫn tương đối đến một thư mục dẫn đến FileInputStream (File), được định nghĩa để ném FileNotFoundException nếu tệp là một thư mục.
Andy Thomas

52

Đây là mã
Nguồn : forum.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);
        }
    }
}  

Nếu bạn đang sử dụng Spring Hãy xem PathMatchingResourcePotypeResolver


3
Người hỏi biết cách triển khai nó bằng các lớp java tiêu chuẩn, nhưng anh ta muốn biết làm thế nào anh ta có thể sử dụng các thư viện hiện có (Spring, Apache Commons).
Jeroen Rosenberg

@Jeroen Rosenberg Cũng có một cách khác được đưa ra cuối cùng đã được chọn :)
Jigar Joshi

7
Tôi thấy giải pháp này hữu ích cho các trường hợp bạn không sử dụng Spring - thật vô lý khi chỉ thêm một phụ thuộc vào Spring bởi vì tính năng này. Cảm ơn! "Bẩn" nhưng hữu ích :) PS One có thể muốn sử dụng File.pathSeparatorthay vì mã hóa cứng :trong getResourcesphương thức.
Timur

5
Ví dụ mã phụ thuộc vào hệ thống, sử dụng thay thế: final String[] classPathElements = classPath.split(System.pathSeparator);
biên dịch

1
Hãy cẩn thận: Thuộc tính hệ thống java. Class.path có thể không chứa đường dẫn lớp thực tế mà bạn đang chạy. Bạn cũng có thể nhìn vào trình tải lớp và hỏi nó về các URL mà nó tải các lớp từ đó. Tất nhiên, một lưu ý khác là nếu bạn đang thực hiện việc này trong SecurityManager, thì hàm tạo ZipFile có thể từ chối quyền truy cập của bạn vào tệp, vì vậy bạn nên nắm bắt điều đó.
Trejkaz

24

Sử dụng phản xạ

Nhận mọi thứ trên đường dẫn:

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

Một ví dụ khác - lấy tất cả các tệp có đuôi .csv từ some.package :

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

Có cách nào để đạt được điều tương tự mà không phải nhập phụ thuộc google không? Tôi muốn tránh để có sự phụ thuộc này chỉ để thực hiện nhiệm vụ này.
Enrico Giurin

1
Phản ánh không phải là một thư viện Google.
Luke Hutchison

Có lẽ đó là trong quá khứ. Câu trả lời của tôi là từ năm 2015 và tôi đã tìm thấy một số tài liệu tham khảo cho thư viện bằng cụm từ "Google Reflections" trước năm 2015. Tôi thấy mã đã di chuyển một chút và đã chuyển từ code.google.com tại đây sang github tại đây
NS du Toit

@NSduToit có lẽ là một vật phẩm lưu trữ mã trên Google Code (không phải là lỗi hiếm gặp), không phải là khiếu nại về quyền tác giả của Google.
matt b

17

Nếu bạn sử dụng apache commonsIO, bạn có thể sử dụng cho hệ thống tệp (tùy chọn có bộ lọc mở rộng):

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

và đối với tài nguyên / classpath:

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

Nếu bạn không biết "directoy /" có trong hệ thống tập tin hay trong tài nguyên, bạn có thể thêm một

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

hoặc là

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

trước các cuộc gọi và sử dụng cả hai kết hợp ...


23
Tệp! = Tài nguyên
djechlin

3
Chắc chắn, tài nguyên có thể không phải luôn luôn là các tệp nhưng câu hỏi là về các tài nguyên có nguồn gốc từ các tệp / thư mục. Vì vậy, hãy sử dụng mã ví dụ nếu bạn muốn kiểm tra các vị trí khác nhau, ví dụ: nếu bạn có một tệp cấu hình trong tài nguyên của mình cho một số cấu hình mặc định và có thể tải một tệp cấu hình bên ngoài thay vào đó nếu nó tồn tại ...
Rob

2
Và tại sao các hồ sơ không phải là tập tin? Các bản báo cáo là các tệp được lưu trữ vào kho lưu trữ zip. Chúng được tải vào bộ nhớ và được truy cập theo cách cụ thể nhưng tôi không thể hiểu tại sao chúng không phải là tệp. Bất kỳ tài nguyên nào không phải là 'một tệp trong kho lưu trữ'?
Mike

10
Không hoạt động (NullPulumException) khi tài nguyên đích là một thư mục nằm trong Tệp JAR (tức là JAR chứa ứng dụng Chính và thư mục đích)
Carlitos Way

12

Vì vậy, theo thuật ngữ của PathMatchingResourcePotypeResolver, đây là những gì cần thiết trong mã:

@Autowired
ResourcePatternResolver resourceResolver;

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

chỉ là một bổ sung nhỏ nếu bạn muốn tìm kiếm tất cả các tài nguyên có thể trong tất cả các đường dẫn lớp bạn cần sử dụngclasspath*:config/*.xml
revau.lt

5

Các Spring framework's PathMatchingResourcePatternResolverlà thực sự tuyệt vời cho những điều sau:

private Resource[] getXMLResources() throws IOException
{
    ClassLoader classLoader = MethodHandles.lookup().getClass().getClassLoader();
    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader);

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

Phụ thuộc Maven:

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

5

Được sử dụng kết hợp phản ứng của 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
}

làm thế nào để có được tất cả các tài nguyên: tức là bắt đầu bằng một trong hai /' or .` hoặc ./không có tài nguyên nào thực sự hoạt động
javadba

3

Với mùa xuân thật dễ dàng. Có thể là một tệp, hoặc thư mục, hoặc thậm chí nhiều tệp, có nhiều khả năng, bạn có thể thực hiện thông qua tiêm.

Ví dụ này cho thấy việc tiêm nhiều tệp nằm trong x/y/zthư mục.

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

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

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

Nó không hoạt động cho các tài nguyên trong hệ thống tập tin cũng như trong JAR.


2
Tôi muốn lấy tên thư mục trong / BOOT-INF / class / static / truyện. Tôi thử mã của bạn với @Value ("classpath: static / truyện / *") và nó trả về một danh sách trống khi chạy JAR. Nó hoạt động cho các tài nguyên trong đường dẫn lớp, nhưng không hoạt động khi chúng ở trong JAR.
PS

@Value ("classpath: x / y / z / *") tài nguyên riêng [] tài nguyên; đã lừa tôi Có giờ tìm kiếm cho điều đó! Cảm ơn!
Rudolf Schmidt

3

Điều này sẽ hoạt động (nếu mùa xuân không phải là một lựa chọn):

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;
}

Điều này đã nhận được một downvote gần đây - nếu bạn tìm thấy một vấn đề với nó, xin vui lòng chia sẻ, cảm ơn bạn!
fl0w

1
Cảm ơn bạn @ArnoUnkrig - vui lòng chia sẻ giải pháp cập nhật của bạn nếu bạn thích
fl0w

3

Cách của tôi, không có Spring, được sử dụng trong bài kiểm tra đơn vị:

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);
}

không hoạt động, khi bạn chạy một jar: java.nio.file.FileSystemNotFoundException tại Paths.get (uri)
error1009

1

Cơ chế mạnh mẽ nhất để liệt kê tất cả các tài nguyên trong đường dẫn hiện đang sử dụng mẫu này với ClassGraph , vì nó xử lý mảng cơ chế đặc tả đường dẫn rộng nhất có thể , bao gồm cả hệ thống mô-đun JPMS mới. (Tôi là tác giả của ClassGraph.)

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

0

Tôi nghĩ bạn có thể tận dụng [ Nhà cung cấp hệ thống tệp Zip ] [1] để đạt được điều này. Khi đang sử dụngFileSystems.newFileSystem có vẻ như bạn có thể coi các đối tượng trong ZIP đó là tệp "thông thường".

Trong các tài liệu liên kết ở trên:

Chỉ định các tùy chọn cấu hình cho hệ thống tệp zip trong đối tượng java.util.Map được truyền cho FileSystems.newFileSystem phương thức. Xem chủ đề [Thuộc tính hệ thống tệp Zip] [2] để biết thông tin về các thuộc tính cấu hình dành riêng cho nhà cung cấp cho hệ thống tệp zip.

Khi bạn có một phiên bản của hệ thống tệp zip, bạn có thể gọi các phương thức của các lớp [ java.nio.file.FileSystem] [3] và [ java.nio.file.Path] [4] để thực hiện các thao tác như sao chép, di chuyển và đổi tên tệp, cũng như sửa đổi các thuộc tính tệp.

Tài liệu cho jdk.zipfsmô-đun ở [trạng thái Java 11] [5]:

Nhà cung cấp hệ thống tệp zip coi tệp zip hoặc tệp JAR là một hệ thống tệp và cung cấp khả năng thao tác với nội dung của tệp. Nhà cung cấp hệ thống tệp zip có thể được tạo bởi [ FileSystems.newFileSystem] [6] nếu được cài đặt.

Đây là một ví dụ giả định tôi đã sử dụng tài nguyên mẫu của bạn. Lưu ý rằng a .ziplà a .jar, nhưng bạn có thể điều chỉnh mã của mình để sử dụng tài nguyên đường dẫn:

Thiết lập

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);
    }
  }

}

Đầu ra

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

0

Không có câu trả lời nào phù hợp với tôi mặc dù tôi đã đặt tài nguyên của mình vào các thư mục tài nguyên và làm theo các câu trả lời ở trên. Điều gì đã làm cho một mẹo là:

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

-5

Dựa trên thông tin của @rob ở trên, tôi đã tạo ra triển khai mà tôi sẽ phát hành cho miền công cộng:

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
}

Mặc dù tôi không mong đợi sẽ getResourcesAsStreamhoạt động như vậy trên một thư mục, nhưng nó thực sự hoạt động và nó hoạt động tốt.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.