如何从类路径资源文件夹中获取文件列表?

3

我想从资源文件夹中设置JFX ImageView的图像,但似乎无法获得不会抛出异常的适当URL/String文件路径。

var x = getRandomImageFromPackage("pictures").toString();
var y = getClass().getClassLoader().getResource("pictures/mindwave/active/Super Saiyan.gif").toString();
this.iconImageView.setImage(new Image(x));

x 返回

/home/sarah/Desktop/Dropbox/School/Current/MAX MSP Utilities/MindWaveMobileDataServer/target/classes/pictures/0515e3b7cb30ac92ebfe729440870a5c.jpg

y返回的结果看起来像:

file:/home/sarah/Desktop/Dropbox/School/Current/MAX%20MSP%20Utilities/MindWaveMobileDataServer/target/classes/pictures/mindwave/active/Super%20Saiyan.gif

在理论上,这两种方法都是可行的,但只有在下面的 setImage(String) 行中放置 x 才会抛出异常。
有没有办法获得包中图像的列表,以便我可以选择一个随机的并设置 ImageView?
我知道有一种自定义扫描器选项,但似乎已经过时了(超过11年,当时也没有得到很好的支持): 日程:
/**
 * Gets a picture from the classpath pictures folder.
 *
 * @param packageName The string path (in package format) to the classpath
 * folder
 * @return The random picture
 */
private Path getRandomImageFromPackage(String packageName) {
    try {
        var list = Arrays.asList(new File(Thread.currentThread().getContextClassLoader().getResource(packageName)
                .toURI()).listFiles());
        var x = list.get(new Random().nextInt(list.size())).toString();
        return list.get(new Random().nextInt(list.size())).toPath();

    } catch (URISyntaxException ex) {
        throw new IllegalStateException("Encountered an error while trying to get a picture from the classpath"
                + "filesystem", ex);
    }
}

以下是参考资源文件夹:

在此输入图片描述


2
查看这个答案,了解列出资源(或执行文件系统查询)的最新方法。它至少适用于默认文件系统,以及jar文件和模块图像。如果其他部署形式的作者足够关心添加文件系统实现,那么它们也可能适用。 - Holger
2个回答

4

你的方法存在问题

你没有一个格式良好的URL

new Image(String url)需要一个URL作为参数。

空格不是URL的有效字符:

这就是为什么你的x字符串不是一个有效的URL,不能用来构建一个图像。

你需要提供Image构造函数识别的输入

请注意,稍微有点复杂,因为从Image javadoc中可以看到,url参数可能是一些其他的东西,但即使如此,它们也都与你尝试查找的内容不匹配。

如果将URL字符串传递给构造函数,则可以是以下任何一种情况:
  1. 可以由此线程的上下文类加载器解析的资源名称
  2. 可以由文件解析的文件路径
  3. 可以由URL解析并存在协议处理程序的URL
除了已注册应用程序的协议处理程序外,还支持RFC 2397“数据”方案的URL。 如果URL使用“数据”方案,则数据必须是base64编码的,并且MIME类型必须为空或图像类型的子类型。 你假设资源在文件系统中,但这不总是有效的。 如果将资源打包到jar中,则无法正常工作。
Arrays.asList(
    new File(
        Thread.currentThread()
            .getContextClassLoader()
            .getResource(packageName)
            .toURI()
    ).listFiles()
);

这样做行不通,因为JAR包中的文件是使用jar:协议而不是file:协议定位的。因此,您将无法从getResource返回的jar:协议URI创建File对象。 推荐方法:使用Spring 从jar获取资源列表实际上是一件相当棘手的事情。从您链接的问题中,最简单的解决方案是使用 不幸的是,这意味着需要依赖于Spring框架才能使用它,这对于这个任务来说是完全过度的......但是我不知道还有什么其他简单而强大的解决方案。但至少您可以调用Spring实用程序类,您不需要启动整个Spring依赖注入容器来使用它,所以您实际上不需要了解任何Spring或遭受任何Spring开销就可以这样做。
因此,您可以编写像这样的内容(ResourceLister是我创建的一个类,以及toURL方法,请参见示例应用程序):
public List<String> getResourceUrls(String locationPattern) throws IOException {
    ClassLoader classLoader = ResourceLister.class.getClassLoader();
    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader);

    Resource[] resources = resolver.getResources(locationPattern);

    return Arrays.stream(resources)
            .map(this::toURL)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
}

可执行示例

ResourceLister.java

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

public class ResourceLister {
    // currently, only gets pngs, if needed, can add
    // other patterns and union the results to get 
    // multiple image types. 
    private static final String IMAGE_PATTERN =
            "classpath:/img/*.png";

    public List<String> getImageUrls() throws IOException {
        return getResourceUrls(IMAGE_PATTERN);
    }

    public List<String> getResourceUrls(String locationPattern) throws IOException {
        ClassLoader classLoader = ResourceLister.class.getClassLoader();
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader);

        Resource[] resources = resolver.getResources(locationPattern);

        return Arrays.stream(resources)
                .map(this::toURL)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    private String toURL(Resource r) {
        try {
            if (r == null) {
                return null;
            }

            return r.getURL().toExternalForm();
        } catch (IOException e) {
            return null;
        }
    }

    public static void main(String[] args) throws IOException {
        ResourceLister lister = new ResourceLister();
        System.out.println(lister.getImageUrls());
    }
}

AnimalApp.java

import javafx.application.Application;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

public class AnimalApp extends Application {
    private static final double ANIMAL_SIZE = 512;

    // remove the magic seed if you want a different random sequence all the time.
    private final Random random = new Random(42);

    private final ResourceLister resourceLister = new ResourceLister();

    private List<Image> images;

    @Override
    public void init() {
        List<String> imageUrls = findImageUrls();
        images = imageUrls.stream()
                .map(Image::new)
                .collect(Collectors.toList());
    }

    @Override
    public void start(Stage stage) {
        ImageView animalView = new ImageView();
        animalView.setFitWidth(ANIMAL_SIZE);
        animalView.setFitHeight(ANIMAL_SIZE);
        animalView.setPreserveRatio(true);

        Button findAnimalButton = new Button("Find animal");
        findAnimalButton.setOnAction(e ->
                animalView.setImage(randomImage())
        );

        VBox layout = new VBox(10,
                findAnimalButton,
                animalView
        );
        layout.setPadding(new Insets(10));
        layout.setAlignment(Pos.CENTER);

        stage.setScene(new Scene(layout));
        stage.show();
    }

    private List<String> findImageUrls() {
        try {
            return resourceLister.getImageUrls();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return new ArrayList<>();
    }

    /**
     * Chooses a random image.
     *
     * Allows the next random image chosen to be the same as the previous image.
     *
     * @return a random image or null if no images were found.
     */
    private Image randomImage() {
        if (images == null || images.isEmpty()) {
            return null;
        }

        return images.get(random.nextInt(images.size()));
    }

    public static void main(String[] args) {
        launch(args);
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>resource-lister</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>resource-lister</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <junit.version>5.7.1</junit.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>17.0.2</version>
        </dependency>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

图片

放置在src/main/resources/img中。

  • chicken.png
  • cow.png
  • pig.png
  • sheep.png

chicken cow pig sheep

执行命令

设置您的JavaFX SDK安装的VM参数:

-p C:\dev\javafx-sdk-17.0.2\lib --add-modules javafx.controls

我尝试使用“classpath*”前缀来运行此代码,因为我的服务在AWS上部署时(本地工作),Spring的ResourceLoader找不到资源中的文件,但我得到了依赖项列表 jar:file:/app.jar!/ jar:file:/app.jar!/BOOT-INF/classes!/ jar:file:/app.jar!/BOOT-INF/lib/spring-boot-2.7.3.jar!/ jar:file:/app.jar!/BOOT-INF/lib/spring-context-5.3.22.jar!/ jar:file:/app.jar!/BOOT-INF/lib/spring-boot-autoconfigure-2.7.3.jar!/ (等等...) 这可能意味着什么? - Lutosław
@Lutosław 发布一个新问题时,您可以参考这个问题。在新问题中,请尽量包含足够的信息和解释,以便其他人能够理解并复制您所遇到的问题(我无法从您的评论中进行复制)。另外,您也可以使用 mipa的答案 作为备选方案。 - jewelsea

2

这并不是一件容易而可靠的事情。因此我创建并将清单文件放入我的资源文件夹中。因此,在运行时,我可以读取该文件,然后拥有我需要的所有文件名。

以下是一个小测试,展示了我如何创建该文件:

public class ListAppDefaultsInventory {

    @Test
    public void test() throws IOException {
        List<String> inventory = listFilteredFiles("src/main/resources/app-defaults", Integer.MAX_VALUE);
        assertFalse("Directory 'app-defaults' is empty.", inventory.isEmpty());
        System.out.println("# src/main/resources/app-defaults-inventory.txt");
        inventory.forEach(s -> System.out.println(s));
    }
    
    public List<String> listFilteredFiles(String dir, int depth) throws IOException {
        try (Stream<Path> stream = Files.walk(Paths.get(dir), depth)) {
            return stream
                .filter(file -> !Files.isDirectory(file))
                .filter(file -> !file.getFileName().toString().startsWith("."))
                .map(Path::toString)
                .map(s -> s.replaceFirst("src/main/resources/app-defaults/", ""))
                .collect(Collectors.toList());
        }
    }
    
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接