Отримайте список ресурсів із каталогу 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'].

Він повинен працювати як для ресурсів у файловій системі, так і в банках.

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


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

Весняні рамки

Використання PathMatchingResourcePatternResolverз Spring Framework.

Роздуми Ронмамо

Інші методи можуть бути повільними під час виконання для величезних значень CLASSPATH. Більш швидке рішення - використовувати API Reflections від ronmamo , який попередньо компілює пошук під час компіляції.


12
якщо ви використовуєте Роздуми, все, що вам потрібно,new Reflections("my.package", new ResourcesScanner()).getResources(pattern)
zapp

27
Чи працює перше рішення, коли воно виконується з файлу jar? Для мене - це не так. Я можу читати файл таким чином, але не можу читати каталог.
Валентина Чумак

5
Перший метод, описаний у цій відповіді - читання контуру як ресурсу, здається, не працює, коли ресурси знаходяться в тому ж JAR, що і виконуваний код, принаймні з OpenJDK 1.8. Помилка досить дивна - NullPointerException перекидається з глибокої логіки обробки файлів JVM. Це як би дизайнери насправді не передбачили такого використання ресурсів, і є лише часткова реалізація. Навіть якщо це працює в деяких JDK або середовищах, це, схоже, не є документально підтвердженим поведінкою.
Кевін Бун

7
Ви не можете прочитати такий каталог, як це немає, оскільки немає каталогу, але потоки файлів. Ця відповідь наполовину неправильна.
Томас Деко

4
Перший метод, здається, не працює - принаймні при запуску із середовища розробки, перш ніж використовувати ресурси. Виклик getResourceAsStream()з відносним шляхом до каталогу призводить до FileInputStream (Файл), який визначено для викидання 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 Є також інший спосіб, який було обрано остаточно :)
Jigar Joshi

7
Я вважаю це рішення корисним для випадків, коли Ви не використовуєте Spring - було б смішно додавати залежність від Spring тільки через цю функцію. Тож, дякую! "Брудні", але корисні :) PS Можливо, хочете використовувати File.pathSeparatorзамість жорсткого коду :в getResourcesметоді.
Тимур

5
Приклад коду залежить від системи, використовуйте це замість цього: final String[] classPathElements = classPath.split(System.pathSeparator);
dcompiled

1
Caveat: Властивість системи java.class.path може не містити фактичного класового шляху, під яким ви працюєте. Ви також можете переглянути завантажувач класів і допитати його, для яких URL-адрес він завантажує класи. Інший застереження, звичайно, полягає в тому, що якщо ви робите це за допомогою SecurityManager, конструктор ZipFile може відхилити ваш доступ до файлу, тож вам слід це вловлювати.
Трежказ

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

чи є якийсь спосіб досягти того, щоб не імпортувати залежність google? Я хотів би уникати цієї залежності лише для виконання цього завдання.
Енріко Джурін

1
Роздуми - це не бібліотека Google.
Люк Хатчісон

Можливо, це було в минулому. Моя відповідь - з 2015 року, і я знайшов деяку посилання на бібліотеку, використовуючи фразу "Роздуми Google" до 2015 року. Я бачу, що код трохи перемістився і перемістився з code.google.com тут до github тут
NS du Toit

@NSduToit, ймовірно, був артефактом розміщення коду в Google Code (не рідкість помилок), а не претензії на авторство від Google.
matt b

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 ...
Rob

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

Залежна залежність:

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

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.


2
Я хочу отримати назви папок під / BOOT-INF / класи / статичні / історії. Я спробую ваш код за допомогою @Value ("classpath: статичний / історії / *"), і він повертає порожній список під час запуску JAR. Він працює для ресурсів на класі, але не тоді, коли вони є в 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

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

не працює, коли ви запускаєте банку: java.nio.file.FileSystemNotFoundException на Paths.get (uri)
помилка1009

1

Найбільш надійний механізм для перерахування всіх ресурсів на classpath в даний час використовувати цю схему з ClassGraph , оскільки вона обробляє максимально широкий масив механізмів специфікації classpath , включаючи нову модульну систему JPMS. (Я автор ClassGraph.)

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

0

Я думаю, що ви можете скористатися [ Провайдером Zip-файлових систем ] [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], якщо встановлений.

Ось надуманий приклад, який я зробив, використовуючи ваші приклади ресурсів. Зауважте, що a .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

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

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що так працювати в каталозі, це дійсно так і працює добре.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.