Получить список ресурсов из каталога classpath

247

Я ищу способ получить список всех имен ресурсов из заданного каталога classpath, что-то вроде метода List<String> getResourceNames (String directoryName).

Например, если каталог путь к классам , x/y/zсодержащий файлы a.html, b.html, c.htmlи подкаталог d, getResourceNames("x/y/z")должны возвращать , List<String>содержащий следующие строки: ['a.html', 'b.html', 'c.html', 'd'].

Он должен работать как для ресурсов в файловой системе, так и для jars.

Я знаю, что могу написать быстрый фрагмент с Files, JarFiles и URLs, но я не хочу изобретать велосипед. Мой вопрос, учитывая существующие общедоступные библиотеки, какой самый быстрый способ реализовать getResourceNames? Стеки Spring и Apache Commons возможны.

viaclectic
источник
1
Соответствующий ответ: stackoverflow.com/a/30149061/4102160
Cfx
Проверить этот проект, решает ресурсы сканирования папок: github.com/fraballi/resources-folder-scanner
Феликс Абалли

Ответы:

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

Spring Framework

Используйте PathMatchingResourcePatternResolverиз Spring Framework.

Ронмамо Размышления

Другие методы могут быть медленными во время выполнения для огромных значений CLASSPATH. Более быстрое решение - использовать Reflections API ronmamo , который прекомпилирует поиск во время компиляции.

iirekm
источник
12
если вы используете Reflections, все, что вам действительно нужно:new Reflections("my.package", new ResourcesScanner()).getResources(pattern)
zapp
27
Первое решение работает, когда оно выполняется из файла JAR? Для меня это не так. Я могу прочитать файл таким способом, но не могу прочитать каталог.
Валентина Чумак
5
Первый метод, описанный в этом ответе - чтение пути как ресурса, похоже, не работает, когда ресурсы находятся в том же JAR-файле, что и исполняемый код, по крайней мере с OpenJDK 1.8. Ошибка довольно странная - исключение NullPointerException создается глубоко в логике обработки файлов JVM. Как будто дизайнеры на самом деле не ожидали такого использования ресурсов, и есть только частичная реализация. Даже если это работает на некоторых JDK или средах, это не похоже на документированное поведение.
Кевин Бун
7
Вы не можете читать каталог, как это, так как нет никакого каталога, кроме файловых потоков. Этот ответ наполовину неверен.
Томас Деко
4
Первый метод, похоже, не работает - по крайней мере, при запуске из среды разработки, перед тем как собирать ресурсы. Вызов getResourceAsStream()с относительным путем к каталогу приводит к FileInputStream (File), который определен как исключение FileNotFoundException, если файл является каталогом.
Энди Томас
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
Спрашивающий знает, как реализовать это, используя стандартные классы Java, но он хочет знать, как он может использовать существующие библиотеки (Spring, Apache Commons).
Йерун Розенберг
@Jeroen Rosenberg Существует еще один способ, который был окончательно выбран :)
Джигар Джоши
7
Я считаю это решение полезным для случаев, когда вы не используете Spring - было бы смешно добавлять зависимость от Spring только из-за этой функции. Так что спасибо тебе! «Грязные» , но полезно :) PS Можно было бы хотеть использовать File.pathSeparatorвместо жестко закодированы :в getResourcesметоде.
Тимур
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);

Другой пример - получить все файлы с расширением .csv из some.package :

Reflections reflections = new Reflections("some.package", new ResourcesScanner());
Set<String> fileNames = reflections.getResources(Pattern.compile(".*\\.csv"));
NS du Toit
источник
Есть ли способ добиться того же без импорта зависимости Google? Я хотел бы избежать этой зависимости только для выполнения этой задачи.
Энрико Джурин
1
Размышления не является библиотекой Google.
Люк Хатчисон
Может быть, это было в прошлом. Мой ответ с 2015 года, и я нашел некоторые ссылки на библиотеку, используя фразу «Google Reflections» до 2015 года. Я вижу, что код немного переместился и перешел с code.google.com здесь на github здесь
NS du Toit
@NSduToit, вероятно, был артефактом размещения кода в Google Code (не редкая ошибка), а не требованием авторства со стороны Google.
Мэтт б
17

Если вы используете apache commonsIO, вы можете использовать его для файловой системы (опционально с расширением фильтра):

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

и для ресурсов / classpath:

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
Файлы! = Ресурсы
djechlin
3
Конечно, ресурсы не всегда могут быть файлами, но вопрос был о ресурсах, происходящих из файлов / каталогов. Так что используйте пример кода, если вы хотите проверить другое местоположение, например, если у вас есть config.xml в ваших ресурсах для некоторой конфигурации по умолчанию, и должна быть возможность загрузить внешний config.xml вместо этого, если он существует ...
Роб
2
А почему ресурсы не являются файлами? Ресурсы - это файлы, заархивированные в zip-архив. Они загружаются в память и доступны определенным образом, но я не понимаю, почему они не являются файлами. Любой пример ресурса, который не является «файлом в архиве»?
Майк
10
Не работает (NullPointerException), когда целевой ресурс является каталогом, который находится в файле JAR (т. Е. JAR содержит главное приложение и целевой каталог)
Carlitos Way,
12

Так что с точки зрения PathMatchingResourcePatternResolver это то, что нужно в коде:

@Autowired
ResourcePatternResolver resourceResolver;

public void getResources() {
  resourceResolver.getResources("classpath:config/*.xml");
}
Павел Котлов
источник
просто небольшое дополнение, если вы хотите найти все возможные ресурсы во всем пути к классам, который вам нужно использоватьclasspath*:config/*.xml
revau.lt
5

В Spring framework«s 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");
}

Maven зависимость:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>LATEST</version>
</dependency>
BullyWiiPlaza
источник
5

Использовал комбинацию ответа Роба.

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

С весной это легко. Будь то файл, папка или даже несколько файлов, есть вероятность, что вы можете сделать это с помощью инъекции.

Этот пример демонстрирует внедрение нескольких файлов, расположенных в 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.

naXa
источник
2
Я хочу получить имена папок в / BOOT-INF / classes / static / Stories. Я пробую ваш код с помощью @Value ("classpath: static / Stories / *"), и он возвращает пустой список при запуске JAR. Это работает для ресурсов в classpath, но не когда они находятся в JAR.
PS
@Value ("classpath: x / y / z / *") частные ресурсы Resource []; сделал трюк со мной. Имейте часы поиска для этого! Спасибо!
Рудольф Шмидт
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
источник
Это недавно получило отрицательный отзыв - если вы обнаружили проблему с ним, пожалуйста, поделитесь, спасибо!
fl0w
1
Спасибо @ArnoUnkrig - поделитесь, пожалуйста, вашим обновленным решением
fl0w
3

По-моему, 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 в 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 как «обычный» файл.

В связанной документации выше:

Укажите параметры конфигурации для файловой системы zip в объекте java.util.Map, переданном в FileSystems.newFileSystem методу. См. Раздел [Свойства файловой системы Zip] [2] для получения информации о свойствах конфигурации конкретного провайдера для файловой системы zip.

Получив экземпляр файловой системы zip, вы можете вызывать методы классов [ java.nio.file.FileSystem] [3] и [ java.nio.file.Path] [4] для выполнения таких операций, как копирование, перемещение и переименование файлов, а также изменение атрибутов файла.

Документация для jdk.zipfsмодуля в [состояниях Java 11] [5]:

Поставщик файловой системы zip рассматривает файл zip или JAR как файловую систему и предоставляет возможность манипулировать содержимым файла. Поставщик файловой системы zip может быть создан [ FileSystems.newFileSystem] [6], если он установлен.

Вот надуманный пример, который я сделал, используя ваши примеры ресурсов. Обратите внимание, что .zipэто.jar , но вы можете адаптировать свой код, чтобы вместо этого использовать ресурсы classpath:

Настроить

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
mkobit
источник
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будет работать с каталогом, он действительно работает и работает хорошо.

Девен Филлипс
источник