Получить список ресурсов из каталога classpath
Я ищу способ получить список всех имен ресурсов из заданного каталога classpath, что-то вроде метода List<String> getResourceNames (String directoryName)
,
Например, учитывая каталог classpath x/y/z
содержащие файлы a.html
, b.html
, c.html
и подкаталог d
, getResourceNames("x/y/z")
должен вернуть List<String>
содержит следующие строки:['a.html', 'b.html', 'c.html', 'd']
,
Он должен работать как для ресурсов в файловой системе, так и для jars.
Я знаю, что могу написать быстрый фрагмент с File
s, JarFile
с и URL
с, но я не хочу изобретать велосипед. Мой вопрос, учитывая существующие общедоступные библиотеки, какой самый быстрый способ реализовать getResourceNames
? Стеки Spring и Apache Commons возможны.
17 ответов
Пользовательский сканер
Реализуйте свой собственный сканер. Например:
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. Более быстрое решение состоит в том, чтобы использовать API Reflections от ronmamo, который прекомпилирует поиск во время компиляции.
Вот код
Источник: 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
Использование Google Reflections:
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"));
Так что с точки зрения PathMatchingResourcePatternResolver это то, что нужно в коде:
@Autowired
ResourcePatternResolver resourceResolver;
public void getResources() {
resourceResolver.getResources("classpath:config/*.xml");
}
Наиболее надежным механизмом для перечисления всех ресурсов в пути к классам в настоящее время является использование этого шаблона с ClassGraph, поскольку он обрабатывает максимально широкий спектр механизмов спецификации пути к классам, включая новую модульную систему JPMS. (Я автор ClassGraph.)
List<String> resourceNames;
try (ScanResult scanResult = new ClassGraph().whitelistPaths("x/y/z").scan()) {
resourceNames = scanResult.getAllResources().getNames();
}
Если вы используете 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)
до звонков и использовать как в комбинации...
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>
Это должно работать (если весна не вариант):
public static List<String> getFilenamesForDirnameFromCP(String directoryName) throws URISyntaxException, UnsupportedEncodingException, IOException {
List<String> filenames = new ArrayList<String>();
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;
}
Мой способ, не 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);
}
Использовал комбинацию ответа Роба.
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
}
С весной это легко. Будь то файл, папка или даже несколько файлов, есть вероятность, что вы можете сделать это с помощью инъекции.
Этот пример демонстрирует внедрение нескольких файлов, расположенных в x/y/z
папка.
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
@Service
public class StackruService {
@Value("classpath:x/y/z/*")
private Resource[] resources;
public List<String> getResourceNames() {
return Arrays.stream(resources)
.map(Resource::getFilename)
.collect(Collectors.toList());
}
}
Это работает для ресурсов в файловой системе, а также в JAR.
преобразователь-шаблона-соответствия пути
Я извлек Spring FrameworkPathMatchingResourcePatternResolver
в отдельную библиотеку для тех, кто не может или не хочет импортировать всю библиотеку Spring Framework.
GitHub
https://github.com/SecretX33/path-matching-resource-pattern-resolver
Добавьте зависимость
Мавен
<dependency>
<groupId>io.github.secretx33</groupId>
<artifactId>path-matching-resource-pattern-resolver</artifactId>
<version>0.1</version>
</dependency>
Градл
implementation 'io.github.secretx33:path-matching-resource-pattern-resolver:0.1'
Градл (КТС)
implementation("io.github.secretx33:path-matching-resource-pattern-resolver:0.1")
Пример использования
Найдите один ресурс
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource resource = resolver.getResource("classpath:com/example/config.properties");
if (resource.exists()) {
// Process the resource
}
Найдите несколько ресурсов
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath:com/example/**/*.xml");
for (Resource resource : resources) {
// Process the resource
}
Я думаю, что вы можете использовать [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
Ни один из ответов не сработал для меня, хотя мои ресурсы были помещены в папки ресурсов и я следовал приведенным выше ответам. Вот что получилось сделать:
@Value("file:*/**/resources/**/schema/*.json")
private Resource[] resources;
Для корневого каталога:
List<String> fileNames = IOUtils.readLines(MyClass.class.getClassLoader().getResourceAsStream("."), StandardCharsets.UTF_8);
Расширяя ответ user3950982 выше, используя его библиотеку ClassGraph , я смог легко получить список всех файлов в папке ресурсов практически без усилий.
Предположим, что в папке ресурсов у вас есть папка с именем
MyImages
. Вот как легко получить список URL-адресов всех файлов в этой папке:
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ResourceList;
import io.github.classgraph.ScanResult;
public static LinkedList<URL> getURLList (String folder) {
LinkedList<URL> urlList = new LinkedList<>();
ScanResult scanResult = new ClassGraph().enableAllInfo().scan();
ResourceList resources = scanResult.getAllResources();
for (URL url : resources.getURLs()) {
if (url.toString().contains(folder)) {
urlList.addLast(url);
}
}
return urlList;
}
Затем вы просто делаете это:
LinkedList<URL> myURLFileList = getURLList("MyImages");
Затем URL-адреса можно загрузить в потоки или использовать FileUtils Apache для копирования файлов в другое место, например:
String outPath = "/My/Output/Path";
for(URL url : myURLFileList) {
FileUtils.copyURLToFile(url, new File(outPath, url.getFile()));
}
Я думаю, что ClassGraph — довольно удобная библиотека, позволяющая делать подобные задачи очень простыми и понятными.
Основываясь на информации @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
работать так над каталогом, это действительно работает, и это работает хорошо.