클래스 경로 디렉토리에서 자원 목록 가져 오기


247

주어진 클래스 경로 디렉토리에서 메소드와 같은 모든 리소스 이름 목록을 얻는 방법을 찾고 List<String> getResourceNames (String directoryName)있습니다.

예를 들어, 클래스 경로 디렉토리 주어진 x/y/z파일 포함 a.html, b.html, c.html및 하위 디렉토리를 d, getResourceNames("x/y/z")A는 반환해야합니다 List<String>다음과 같은 문자열을 포함 : ['a.html', 'b.html', 'c.html', 'd'].

파일 시스템 및 jar의 자원 모두에서 작동해야합니다.

Files, JarFiles 및 URLs 로 빠른 스 니펫을 작성할 수 있지만 바퀴를 재발 명하고 싶지 않습니다. 내 질문은 기존의 공개 라이브러리를 고려할 때 구현하는 가장 빠른 방법은 무엇 getResourceNames입니까? Spring과 Apache Commons 스택은 모두 가능합니다.



이 프로젝트를 확인하고 리소스 폴더 스캔을 해결하십시오. github.com/fraballi/resources-folder-scanner
Felix Aballi

답변:


165

맞춤형 스캐너

자신의 스캐너를 구현하십시오. 예를 들면 다음과 같습니다.

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

스프링 프레임 워크

PathMatchingResourcePatternResolverSpring Framework에서 사용하십시오 .

Ronmamo 반사

다른 CLASSPATH 값은 런타임에 느릴 수 있습니다. 더 빠른 해결책은 컴파일 타임에 검색을 사전 컴파일하는 ronmamo의 Reflections API 를 사용하는 것 입니다.


12
Reflections를 사용하는 경우 실제로 필요한 것은 다음과 같습니다.new Reflections("my.package", new ResourcesScanner()).getResources(pattern)
zapp

27
jar 파일에서 실행될 때 첫 번째 솔루션이 작동합니까? 나를 위해-그렇지 않습니다. 이 방법으로 파일을 읽을 수는 있지만 디렉토리를 읽을 수는 없습니다.
Valentina Chumak

5
이 답변에 설명 된 첫 번째 방법-경로를 리소스로 읽는 것은 리소스가 최소한 OpenJDK 1.8에서 실행 코드와 동일한 JAR에있을 때 작동하지 않는 것 같습니다. JVM의 파일 처리 로직에서 NullPointerException이 발생합니다. 마치 디자이너가 실제로 이러한 리소스 사용을 기대하지 않았으며 부분적으로 만 구현 된 것처럼 보입니다. 일부 JDK 또는 환경에서 작동하더라도 문서화 된 동작은 아닙니다.
Kevin Boone

7
파일 스트림 외에 디렉토리가 없기 때문에 이와 같은 디렉토리를 읽을 수 없습니다. 이 대답은 반 틀 렸습니다.
토마스 Decaux

4
첫 번째 방법은 리소스를 다루기 전에 적어도 개발 환경에서 실행할 때 작동하지 않는 것으로 보입니다. getResourceAsStream()디렉토리에 대한 상대 경로 를 사용하여 호출 하면 FileInputStream (File)이 생겨 파일이 디렉토리 인 경우 FileNotFoundException을 발생 시키도록 정의됩니다.
Andy Thomas

52

여기에 코드입니다
출처 : 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를 살펴보십시오 .


3
Questioner는 표준 Java 클래스를 사용하여 구현하는 방법을 알고 있지만 기존 (Spring, Apache Commons) 라이브러리를 어떻게 활용할 수 있는지 알고 싶어합니다.
Jeroen Rosenberg

@Jeroen Rosenberg 마지막으로 선택된 또 다른 방법이 있습니다 :)
Jigar Joshi

7
이 솔루션이 Spring을 사용하지 않는 경우에 유용하다는 것을 알았습니다.이 기능의 Spring becuase에만 의존성을 추가하는 것은 어리석은 일입니다. 감사합니다! "더러운"하지만 유용 :) PS 하나 는 방법 File.pathSeparator으로 하드 코딩 된 대신에 사용하고 싶을 수도 있습니다 . :getResources
Timur

5
코드 예제는 시스템에 따라 다릅니다. 대신 다음을 사용하십시오. final String[] classPathElements = classPath.split(System.pathSeparator);
dcompiled

1
주의 사항 : java.class.path 시스템 프로퍼티에는 실제 실행중인 클래스 경로가 포함되어 있지 않을 수 있습니다. 또는 클래스 로더를보고 클래스를로드하는 URL을 조사 할 수 있습니다. 물론 다른 경고는 SecurityManager에서이 작업을 수행하는 경우 ZipFile 생성자가 파일에 대한 액세스를 거부 할 수 있다는 점입니다.
Trejkaz

24

반사 사용

클래스 패스에있는 모든 것을 얻는다 :

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> fileNames = reflections.getResources(Pattern.compile(".*\\.csv"));

구글 의존성을 가져올 필요없이 동일한 것을 달성 할 수있는 방법이 있습니까? 이 작업을 수행하기 위해이 종속성을 피하고 싶습니다.
Enrico Giurin

1
리플렉션은 Google 라이브러리가 아닙니다.
Luke Hutchison

아마도 과거에 있었을 것입니다. 내 대답은 2015 년이고 나는 문구가 나는 코드가 조금 주위에 이동하고 code.google.com에서 이동 참조 2015 년 전에 "구글 반사"를 사용하여 라이브러리에 일부 참조를 찾았어요 여기 GitHub의에 여기
NS 뒤 투아을

@NSduToit는 아마도 Google의 저작자 주장이 아니라 Google 코드에서 코드를 호스팅하는 인공물 일 것입니다 (흔치 않은 실수는 아님).
매트 b

17

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)

통화하기 전에 두 가지를 함께 사용하십시오 ...


23
파일! = resources
djechlin

3
물론 리소스는 항상 파일 일 수는 없지만 파일 / 디렉토리에서 시작된 리소스에 대한 문제였습니다. 따라서 다른 위치를 확인하려면 예제 코드를 사용하십시오. 예를 들어 자원에 일부 기본 구성에 대한 config.xml이 있고 외부 config.xml이있는 경우 대신로드 할 수 있어야하는 경우 ...
Rob

2
왜 자원 봉사가 파일이 아닌가? 리소스는 zip 아카이브로 아카이브 된 파일입니다. 그것들은 메모리에로드되고 특정 방식으로 액세스되지만 파일이 아닌 이유를 알 수 없습니다. '아카이브 내의 파일'이 아닌 다른 자원이 있습니까?
Mike

10
대상 리소스가 디렉토리가 JAR 파일 안에 상주 (즉, JAR가 기본 응용 프로그램 및 대상 디렉토리 포함)이다 (NullPointerException이) 작동하지 않습니다
칼리 토스 웨이

12

따라서 PathMatchingResourcePatternResolver의 관점에서 이것은 코드에서 필요한 것입니다.

@Autowired
ResourcePatternResolver resourceResolver;

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

사용해야하는 모든 클래스 경로에서 가능한 모든 리소스를 검색하려면 약간만 추가하십시오classpath*:config/*.xml
revau.lt

5

Spring framework의는 PathMatchingResourcePatternResolver이러한 것들을 정말 굉장합니다 :

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

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

메이븐 의존성 :

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

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 .`로 시작 하거나 ./실제로 작동하지 않는 것
javadba

3

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 StackoverflowService {
    @Value("classpath:x/y/z/*")
    private Resource[] resources;

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

JAR뿐만 아니라 파일 시스템의 리소스에서도 작동합니다.


2
/ BOOT-INF / classes / static / stories 아래에 폴더 이름을 가져오고 싶습니다. @Value ( "classpath : static / stories / *")로 코드를 시도하고 JAR을 실행할 때 빈 목록을 반환합니다. 클래스 경로의 리소스에는 작동하지만 JAR에있을 때는 작동하지 않습니다.
PS

@Value ( "classpath : x / y / z / *") private 리소스 [] 리소스; 나에게 속임수를 썼다. 그것에 대한 검색 시간이 있습니다! 감사!
Rudolf Schmidt

3

이것은 작동해야합니다 (스프링이 옵션이 아닌 경우).

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

최근에 공감대를 받았습니다. 문제가 발견되면 공유하여 주셔서 감사합니다.
fl0w

1
@ArnoUnkrig 감사합니다-원하는 경우 업데이트 된 솔루션을 공유하십시오
fl0w

3

단위 테스트 중에 스프링이 사용되지 않는 방식 :

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)
error1009

1

클래스 경로의 모든 자원을 나열하는 가장 강력한 메커니즘은 현재이 패턴을 ClassGraph와 함께 사용하는 것입니다 . 새로운 JPMS 모듈 시스템을 포함하여 가장 광범위한 클래스 경로 지정 메커니즘을 처리하기 때문 입니다. (저는 ClassGraph의 저자입니다.)

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

0

[ Zip File System Provider ] [1]을 활용하여이를 달성 할 수 있다고 생각합니다 . 사용할 때 FileSystems.newFileSystem해당 ZIP의 객체를 "일반"파일로 취급 할 수 있습니다.

위의 링크 된 문서에서 :

메소드에 전달 된 java.util.Map 오브젝트에서 zip 파일 시스템의 구성 옵션을 지정하십시오 FileSystems.newFileSystem. zip 파일 시스템의 제공자 별 구성 특성에 대한 정보는 [Zip 파일 시스템 특성] [2] 주제를 참조하십시오.

zip 파일 시스템의 인스턴스가 있으면 [ java.nio.file.FileSystem] [3] 및 [ java.nio.file.Path] [4] 클래스 의 메소드를 호출하여 파일 복사, 이동 및 이름 바꾸기와 파일 오퍼레이션 수정과 같은 조작을 수행 할 수 있습니다.

jdk.zipfs[Java 11 상태] [5] 의 모듈에 대한 설명서 :

zip 파일 시스템 제공자는 zip 또는 JAR 파일을 파일 시스템으로 취급하고 파일의 내용을 조작하는 기능을 제공합니다. zip 파일 시스템 제공자는 [ FileSystems.newFileSystem] [6] (설치된 경우)으로 작성할 수 있습니다 .

다음은 예제 리소스를 사용하여 고안 한 예제입니다. a .zip는 a .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

자바

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

0

내 리소스를 리소스 폴더에 넣고 위의 답변을 따랐음에도 불구하고 답변이 모두 효과가 없었습니다. 트릭을 만든 것은 다음과 같습니다.

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

-5

위의 @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디렉토리에서 그렇게 작동 하지는 않았지만 실제로 작동하고 잘 작동합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.