如何确定我的 JavaFX 应用程序所需的 FXML 文件、CSS 文件、图像和其他资源的正确路径?

Posted

技术标签:

【中文标题】如何确定我的 JavaFX 应用程序所需的 FXML 文件、CSS 文件、图像和其他资源的正确路径?【英文标题】:How do I determine the correct path for FXML files, CSS files, Images, and other resources needed by my JavaFX Application? 【发布时间】:2022-01-22 03:46:42 【问题描述】:

我的 JavaFX 应用程序需要能够找到 FXML 文件以使用 FXMLLoader 以及样式表(CSS 文件)和图像加载它们。当我尝试加载这些时,我经常会遇到错误,或者我尝试加载的项目根本无法在运行时加载。

对于 FXML 文件,我看到的错误消息包括

Caused by: java.lang.NullPointerException: location is not set

对于图像,堆栈跟踪包括

Caused by: java.lang.IllegalArgumentException: Invalid URL: Invalid URL or resource not found

我如何找出这些资源的正确资源路径?

【问题讨论】:

由于 JavaFX 标签上有很多关于加载资源的问题,我将这个问答作为社区 wiki 发布。如果您认为可以改进,请编辑问题或答案。 好主意和答案 :) 添加到标签 wiki 中的常见问题解答中,这样我们就可以轻松找到它以进行欺骗关闭。 【参考方案1】:

简短回答:

使用getClass().getResource(...)SomeOtherClass.class.getResource(...) 为资源创建URL 将绝对路径(带前导/)或相对路径(不带前导/)传递给getResource(...) 方法。路径是包含资源的,其中. 替换为/不要在资源路径中使用..。如果并且当应用程序被捆绑为 jar 文件时,这将不起作用。如果资源不在同一个包或类的子包中,请使用绝对路径。 对于 FXML 文件,将 URL 直接传递给 FXMLLoader。 对于图像和样式表,在URL 上调用toExternalForm() 以生成String 以传递给ImageImageView 构造函数,或添加到stylesheets 列表中。 要进行故障排除,请检查您的 build 文件夹(或 jar 文件)的内容,而不是您的 source 文件夹。

完整答案

内容

    此答案的范围 资源在运行时加载 JavaFX 使用 URL 加载资源 资源名称规则 使用getClass().getResource(...) 创建资源 URL 组织代码和资源 Maven(和类似的)标准布局 疑难解答

本答案的范围

请注意,此答案解决了加载资源(例如 FXML 文件、图像和样式表)的问题,这些资源是应用程序的一部分,并与应用程序捆绑在一起。因此,例如,加载用户从运行应用程序的机器上的文件系统中选择的图像将需要不同的技术,此处未介绍。

资源在运行时加载

关于加载资源,首先要了解的是,它们当然是在运行时加载的。通常,在开发过程中,应用程序从文件系统运行:也就是说,运行它所需的类文件和资源是文件系统上的单个文件。但是,一旦构建了应用程序,它通常会从 jar 文件中执行。在这种情况下,FXML 文件、样式表和图像等资源不再是文件系统上的单个文件,而是 jar 文件中的条目。因此:

代码不能使用FileFileInputStreamfile: URL 来加载资源

JavaFX 使用 URL 加载资源

JavaFX 使用 URL 加载 FXML、图像和 CSS 样式表。

FXMLLoader 明确要求将java.net.URL 对象传递给它(传递给static FXMLLoader.load(...) 方法、FXMLLoader 构造函数或setLocation() 方法)。

ImageScene.getStylesheets().add(...) 都期望 Strings 代表 URL。如果 URL 是在没有方案的情况下传递的,则它们将相对于类路径进行解释。这些字符串可以通过在URL 上调用toExternalForm() 以稳健的方式从URL 创建。

为资源创建正确 URL 的推荐机制是使用 Class.getResource(...),它在适当的 Class 实例上调用。这样的类实例可以通过调用getClass()(给出当前对象的类)或ClassName.class来获得。 Class.getResource(...) 方法采用代表资源名称的String

资源名称规则

资源名称是/ 分隔的路径名称。每个组件代表一个包或子包名称组件。 资源名称区分大小写。 资源名称中的各个组件必须是有效的 Java 标识符

最后一点有一个重要的后果:

... 不是有效的 Java 标识符,因此它们不能用于资源名称中

当应用程序从文件系统运行时,这些实际上可能会起作用,尽管这实际上是getResource() 实现的一个意外。当应用程序捆绑为 jar 文件时,它们将失败。

同样,如果您在不区分仅大小写不同的文件名的操作系统上运行,则在从文件系统运行时在资源名称中使用错误的大小写可能会起作用,但从 jar 运行时会失败文件。

/ 开头的资源名称是绝对的:换句话说,它们是相对于类路径进行解释的。没有前导 / 的资源名称将相对于调用 getResource() 的类进行解释。

对此稍有变化的是使用getClass().getClassLoader().getResource(...)。提供给ClassLoader.getResource(...) 的路径不得/ 开头,并且始终是绝对的,即它是相对于类路径的。还需要注意的是,在模块化应用程序中,使用ClassLoader.getResource() 访问资源,在某些情况下,受强封装规则的约束,另外必须无条件打开包含资源的包。详情请见documentation。

使用 getClass().getResource() 创建资源 URL

要创建资源 URL,请使用 someClass.getResource(...)。通常someClass代表当前对象的类,使用getClass()获取。但是,不一定要如此,如下一节所述。

如果资源与当前类在同一个包中,或者在该类的子包中,请使用资源的相对路径:

 // FXML file in the same package as the current class:
 URL fxmlURL = getClass().getResource("MyFile.fxml");
 Parent root = FXMLLoader.load(fxmlURL);

 // FXML file in a subpackage called `fxml`:
 URL fxmlURL2 = getClass().getResource("fxml/MyFile.fxml");
 Parent root2 = FXMLLoader.load(fxmlURL2);

 // Similarly for images:
 URL imageURL = getClass().getResource("myimages/image.png");
 Image image = new Image(imageURL.toExternalForm());

如果资源所在的包不是当前类的子包,请使用绝对路径。例如,如果当前类在包org.jamesd.examples.view中,我们需要加载包style.css中的CSS文件org.jamesd.examples.css,我们必须使用绝对路径:

 URL cssURL = getClass().getResource("/org/jamesd/examples/css/style.css");
 scene.getStylesheets().add(cssURL.toExternalForm());

对于此示例,值得再次强调的是,路径 "../css/style.css" 不包含有效的 Java 资源名称,并且如果应用程序捆绑为 jar 文件,将不起作用

组织代码和资源

我建议将您的代码和资源组织到由它们关联的 UI 部分确定的包中。 Eclipse 中的以下源代码布局给出了这种组织的示例:

使用这种结构,每个资源在同一个包中都有一个类,因此很容易为任何资源生成正确的 URL:

FXMLLoader editorLoader = new FXMLLoader(EditorController.class.getResource("Editor.fxml"));
Parent editor = editorLoader.load();
FXMLLoader sidebarLoader = new FXMLLoader(SidebarController.class.getResource("Sidebar.fxml"));
Parent sidebar = sidebarLoader.load();

ImageView logo = new ImageView();
logo.setImage(newImage(SidebarController.class.getResource("logo.png").toExternalForm()));

mainScene.getStylesheets().add(App.class.getResource("style.css").toExternalForm());

如果你有一个只有资源没有类的包,例如下面布局中的images

您甚至可以考虑创建一个“标记界面”,仅用于查找资源名称:

package org.jamesd.examples.sample.images ;
public interface ImageLocation  

现在可以让您轻松找到这些资源:

Image clubs = new Image(ImageLocation.class.getResource("clubs.png").toExternalForm());

从类的子包中加载资源也相当简单。给定以下布局:

我们可以在App类中加载资源如下:

package org.jamesd.examples.resourcedemo;

import java.net.URL;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class App extends Application 

    @Override
    public void start(Stage primaryStage) throws Exception         
        
        URL fxmlResource = getClass().getResource("fxml/MainView.fxml");
        
        
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(fxmlResource);
        Parent root = loader.load();
        Scene scene = new Scene(root);
        scene.getStylesheets().add(getClass().getResource("style/main-style.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.show();
    
    
    public static void main(String[] args) 
        Application.launch(args);
    


要加载不在您从中加载它们的类的同一个包或子包中的资源,您需要使用绝对路径:

    URL fxmlResource = getClass().getResource("/org/jamesd/examples/resourcedemo/fxml/MainView.fxml");

Maven(和类似的)标准布局

Maven 和其他依赖项管理和构建工具建议使用 source 文件夹布局,其中资源与 Java 源文件分开。上一个示例的 Maven 布局版本如下所示:

了解它是如何构建以组装应用程序很重要:

source文件夹src/main/java中的*.java文件被编译成class文件,部署到build文件夹或jar文件中。 resource 文件夹 src/main/resources 中的资源被复制到构建文件夹或 jar 文件中。

在此示例中,由于资源位于与定义源代码的包的子包相对应的文件夹中,因此生成的构建(默认情况下,Maven 位于target/classes)由单个结构组成。

请注意,src/main/javasrc/main/resources 都被视为构建中相应结构的根,因此只有它们的内容而不是文件夹本身是构建的一部分。换句话说,运行时没有resources 文件夹可用。构建结构如下面的“疑难解答”部分所示。

请注意,本例中的 IDE (Eclipse) 显示 src/main/java 源文件夹与 src/main/resources 文件夹不同;在第一种情况下,它显示 packages,但对于资源文件夹,它显示 folders。确保您知道您是在 IDE 中创建包(其名称为 .-delimited)或文件夹(其名称不得包含 .,或任何其他在 Java 标识符中无效的字符)。

疑难解答

如果您遇到意外错误,请先检查以下内容:

确保您的资源没有使用无效名称。这包括在资源路径中使用...。 确保在预期的位置使用相对路径,在预期的位置使用绝对路径。对于Class.getResource(...),如果路径有前导/,则该路径是绝对路径,否则为相对路径。对于ClassLoader.getResource(...),路径始终是绝对路径,并且不得/ 开头。 请记住,绝对路径是相对于类路径定义的。通常,类路径的根目录是 IDE 中所有源文件夹和资源文件夹的联合。

如果所有这些看起来都正确,但您仍然看到错误,请检查 build 或部署文件夹。此文件夹的确切位置因 IDE 和构建工具而异。如果您使用的是 Maven,则默认为 target/classes。其他构建工具和 IDE 将部署到名为 binclassesbuildout 的文件夹中。

通常,您的 IDE 不会显示构建文件夹,因此您可能需要使用系统文件资源管理器进行检查。

上面 Maven 示例的组合源代码和构建结构是

如果您正在生成 jar 文件,某些 IDE 可能允许您在树视图中展开 jar 文件以检查其内容。也可以在命令行中使用jar tf file.jar查看内容:

$ jar -tf resource-demo-0.0.1-SNAPSHOT.jar 
META-INF/
META-INF/MANIFEST.MF
org/
org/jamesd/
org/jamesd/examples/
org/jamesd/examples/resourcedemo/
org/jamesd/examples/resourcedemo/images/
org/jamesd/examples/resourcedemo/style/
org/jamesd/examples/resourcedemo/fxml/
org/jamesd/examples/resourcedemo/images/so-logo.png
org/jamesd/examples/resourcedemo/style/main-style.css
org/jamesd/examples/resourcedemo/Controller.class
org/jamesd/examples/resourcedemo/fxml/MainView.fxml
org/jamesd/examples/resourcedemo/App.class
module-info.class
META-INF/maven/
META-INF/maven/org.jamesd.examples/
META-INF/maven/org.jamesd.examples/resource-demo/
META-INF/maven/org.jamesd.examples/resource-demo/pom.xml
META-INF/maven/org.jamesd.examples/resource-demo/pom.properties
$ 

如果资源未部署,或部署到意外位置,请检查构建工具或 IDE 的配置。

外部教程参考

一个有用的资源定位外部教程是 Eden coding's tutorial:

Where to put resource files in JavaFX.

Eden 编码教程的好处在于它很全面。除了涵盖此问题中有关从 Java 代码查找的信息之外。 Eden 教程涵盖的主题包括定位在 CSS 中编码为 url 的资源,或使用 @ 说明符或 fx:include 元素在 FXML 中的资源引用(这些主题目前未直接包含在此答案中)。

【讨论】:

很好的补充提到大写/小写处理的潜在差异 - 这就是为什么我更喜欢小写的资源名称(尽管不是命名约定) @kleopatra 某些框架(例如 afterburner.fx 和 FXWeaver)要求控制器类名称与 FXML 名称匹配,这会强制 FXML 名称为大写。 非常好。但请务必在 fxml 中添加有关 url 的部分(例如加载图像)。 关于如何使用 FXML 处理 @ 的评论会很有帮助。即 您可能会考虑对使用类加载器 api 的查找进行一些更改:也许强调它不得有一个前导斜杠(正如 Jewelsea 在您的其他评论中指出的那样回答***.com/a/68913233/203657)

以上是关于如何确定我的 JavaFX 应用程序所需的 FXML 文件、CSS 文件、图像和其他资源的正确路径?的主要内容,如果未能解决你的问题,请参考以下文章

如何确定我的tomcat应用程序所需的Openshift Pod的资源限制?

如何确定特定 win32 api 调用所需的 windows 库?

如何确定光流所需的处理器速度?

运行我的小程序所需的最低 Java 版本

java - 确定任何 webapp 和独立应用程序所需的 -Xmx 和 -Xms 的最佳方法

我们如何确定 ffmpeg 所需的依赖项