提问人:James_D 提问时间:5/1/2020 更新时间:3/17/2023 访问量:16586
如何确定 JavaFX 应用程序所需的 FXML 文件、CSS 文件、图像和其他资源的正确路径?
How do I determine the correct path for FXML files, CSS files, Images, and other resources needed by my JavaFX Application?
问:
我的 JavaFX 应用程序需要能够找到 FXML 文件以加载它们,以及样式表(CSS 文件)和图像。当我尝试加载这些项目时,我经常会遇到错误,或者我尝试加载的项目在运行时根本无法加载。FXMLLoader
对于FXML文件,我看到的错误消息包括
Caused by: java.lang.NullPointerException: location is not set
对于图像,堆栈跟踪包括
Caused by: java.lang.IllegalArgumentException: Invalid URL: Invalid URL or resource not found
如何确定这些资源的正确资源路径?
答:
答案的简短版本:
- 使用 或 创建资源
getClass().getResource(...)
SomeOtherClass.class.getResource(...)
URL
- 将绝对路径(带前导)或相对路径(不带前导)传递给方法。路径是包含资源的包,替换为 .
/
/
getResource(...)
.
/
- 请勿在资源路径中使用。如果应用程序捆绑为 jar 文件,这将不起作用。如果资源不在同一包中或类的子包中,请使用绝对路径。
..
- 对于 FXML 文件,请直接将 .
URL
FXMLLoader
- 对于图像和样式表,调用 以生成要传递给 or 构造函数的 或添加到列表中。
toExternalForm()
URL
String
Image
ImageView
stylesheets
- 要进行故障排除,请检查生成文件夹(或 jar 文件)的内容,而不是源文件夹的内容。
- 在获取资源时放置在路径中总是错误的。该目录仅在开发和生成时可用,在部署和运行时不可用。
src
src
完整答案
内容
- 本答案的范围
- 资源在运行时加载
- JavaFX 使用 URL 加载资源
- 资源作为流
- 资源名称规则
- 创建资源 URL
getClass().getResource(...)
- 组织代码和资源
- Maven(和类似)标准布局
- 故障 排除
本答案的范围
请注意,此答案仅涉及加载资源(例如 FXML 文件、图像和样式表),这些资源是应用程序的一部分,并与之捆绑在一起。因此,例如,加载用户从运行应用程序的计算机上的文件系统中选择的图像将需要此处未介绍的不同技术。
资源在运行时加载
关于加载资源,首先要了解的是,它们当然是在运行时加载的。通常,在开发过程中,应用程序是从文件系统运行的:也就是说,运行它所需的类文件和资源是文件系统上的单个文件。但是,一旦构建了应用程序,它通常是从 jar 文件执行的。在这种情况下,FXML 文件、样式表和图像等资源不再是文件系统上的单个文件,而是 jar 文件中的条目。因此:
代码不能使用 、 或 URL 加载资源
File
FileInputStream
file:
JavaFX 使用 URL 加载资源
JavaFX 使用 URL 加载 FXML、图像和 CSS 样式表。
显式期望将对象传递给它(传递给方法、构造函数或方法)。FXMLLoader
java.net.URL
static
FXMLLoader.load(...)
FXMLLoader
setLocation()
和 期望 s 表示 URL。如果传递的 URL 没有方案,则相对于类路径对其进行解释。这些字符串可以通过调用 以可靠的方式从 创建。Image
Scene.getStylesheets().add(...)
String
URL
toExternalForm()
URL
为资源创建正确 URL 的推荐机制是使用 ,这是在适当的实例上调用的。这样的类实例可以通过调用(给出当前对象的类)或 来获得。该方法采用表示资源名称的 。Class.getResource(...)
Class
getClass()
ClassName.class
Class.getResource(...)
String
资源作为流
如果需要将资源作为流,则可以使用 或 为资源数据创建流。但是,对于 JavaFX 中的大多数工作,您不需要这样做,因为 JavaFX API 主要使用 URL 作为输入而不是流。有一些 API,例如 Image 构造函数和 FXMLLoader,它们可以同时处理 URL 和流。对于此类 API,通常最好使用基于 URL 的 API 表单,而不是基于流的 API 表单。这允许像 FXMLLoader 这样的进程找到所包含资源的相对 URL,如果只是提供原始 InputStream,则无法做到这一点。getClass().getResourceAsStream(...)
SomeOtherClass.class.getResourceAsStream(...)
资源名称规则
- 资源名称是以 - 分隔的路径名。每个组件都表示一个包或子包名称组件。
/
- 资源名称区分大小写。
- 资源名称中的各个组件必须是有效的 Java 标识符
最后一点有一个重要的后果:
.
并且不是有效的 Java 标识符,因此不能在资源名称中使用它们。..
当应用程序从文件系统运行时,这些实际上可能起作用,尽管这实际上更像是实现 的意外。当应用程序捆绑为 jar 文件时,它们将失败。getResource()
同样,如果运行在操作系统上,该操作系统不区分仅大小写不同的文件名,则在资源名称中使用错误的大小写可能会在从文件系统运行时起作用,但在从 jar 文件运行时会失败。
以前导开头的资源名称是绝对的:换句话说,它们是相对于类路径进行解释的。不带前导的资源名称是相对于调用的类进行解释的。/
/
getResource()
对此略有不同,即使用 .提供给的路径不能以 a 开头,并且始终是绝对的,即它是相对于类路径的。还应该注意的是,在模块化应用程序中,在某些情况下,使用资源访问受强封装规则的约束,此外,必须无条件打开包含资源的包。有关详细信息,请参阅文档。getClass().getClassLoader().getResource(...)
ClassLoader.getResource(...)
/
ClassLoader.getResource()
创建资源 URLgetClass().getResource()
要创建资源 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());
如果资源位于不是当前类的子包中的包中,请使用绝对路径。例如,如果当前类在包中,并且我们需要加载包中的CSS文件,则必须使用绝对路径:
org.jamesd.examples.view
style.css
org.jamesd.examples.css
URL cssURL = getClass().getResource("/org/jamesd/examples/css/style.css"); scene.getStylesheets().add(cssURL.toExternalForm());
在此示例中,值得再次强调的是,该路径不包含有效的 Java 资源名称,如果应用程序捆绑为 jar 文件,则该路径将不起作用。
"../css/style.css"
组织代码和资源
我建议将代码和资源组织到由它们关联的 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");
Parent root = FXMLLoader.load(fxmlResource);
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 标准目录布局,Maven 和其他依赖项管理和构建工具建议使用源文件夹布局,其中资源与 Java 源文件分开。上一个示例的 Maven 布局版本如下所示:
了解如何构建它来组装应用程序非常重要:
*.java
源文件夹中的文件被编译为类文件,这些类文件被部署到构建文件夹或 jar 文件中。src/main/java
- 资源文件夹中的资源将复制到 build 文件夹或 jar 文件中。
src/main/resources
在此示例中,由于资源位于与定义源代码的包的子包相对应的文件夹中,因此生成的生成(默认情况下,在 Maven 中位于 )由单个结构组成。target/classes
请注意,这两个 和 都被视为生成中相应结构的根,因此只有它们的内容(而不是文件夹本身)是生成的一部分。换句话说,运行时没有可用的文件夹。构建结构显示在下面的“故障排除”部分中。src/main/java
src/main/resources
resources
请注意,在本例中,IDE (Eclipse) 显示源文件夹的方式与文件夹不同;在第一种情况下,它显示包,但对于资源文件夹,它显示文件夹。确保您知道是在 IDE 中创建包(其名称为 -delimited)还是文件夹(其名称不得包含 ,或在 Java 标识符中无效的任何其他字符)。src/main/java
src/main/resources
.
.
如果您正在使用 Maven,并且为了便于维护,您宁愿将 .fxml 文件保留在引用它们的 .java 文件旁边(而不是严格遵守 Maven 标准目录布局),则可以这样做。只需告诉 Maven 将这些文件复制到输出目录中的同一文件夹,它将把从这些源文件生成的类文件放入该文件夹中,方法是在 pom.xml 文件中包含如下内容:
<build>
...
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.fxml</include>
<include>**/*.css</include>
</includes>
</resource>
...
</build>
如果这样做,则可以使用一种方法,例如让您的类从包含自己的 .class 文件的目录中加载 .fxml 资源。FXMLLoader.load(getClass().getResource("MyFile.fxml"))
故障 排除
如果出现意想不到的错误,请首先检查以下内容:
- 请确保没有为资源使用无效的名称。这包括在资源路径中使用 或。
.
..
- 确保在预期位置使用相对路径,在预期位置使用绝对路径。因为如果路径有前导,则路径是绝对的,否则是相对的。对于 ,路径始终是绝对的,并且不能以 .
Class.getResource(...)
/
ClassLoader.getResource(...)
/
- 请记住,绝对路径是相对于类路径定义的。通常,类路径的根目录是 IDE 中所有源文件夹和资源文件夹的并集。
如果所有这些看起来都是正确的,但仍然看到错误,请检查生成或部署文件夹。此文件夹的确切位置因 IDE 和生成工具而异。如果您使用的是 Maven,则默认情况下为 .其他生成工具和 IDE 将部署到名为 、 、 或 的文件夹中。target/classes
bin
classes
build
out
通常,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 的配置。
示例图像加载故障排除代码
此代码故意比严格要求更详细,以便于为映像加载过程添加其他调试信息。它还使用 System.out 而不是记录器,以便于移植。
String resourcePathString = "/img/wumpus.png";
Image image = loadImage(resourcePathString);
// ...
private Image loadImage(String resourcePathString) {
System.out.println("Attempting to load an image from the resourcePath: " + resourcePathString);
URL resource = HelloApplication.class.getResource(resourcePathString);
if (resource == null) {
System.out.println("Resource does not exist: " + resourcePathString);
return null;
}
String path = resource.toExternalForm();
System.out.println("Image path: " + path);
Image image = new Image(path);
System.out.println("Image load error? " + image.isError());
System.out.println("Image load exception? " + image.getException());
if (!image.isError()) {
System.out.println("Successfully loaded an image from " + resourcePathString);
}
return image;
}
外部教程参考
关于资源位置的一个有用的外部教程是 Eden 编码的教程:
Eden 编码教程的好处是它很全面。除了涵盖有关从本问题中的 Java 代码中查找的信息外。Eden 教程涵盖的主题包括查找在 CSS 中编码为 url 的资源,或使用说明符或元素在 FXML 中引用资源(这些主题目前未在本答案中直接涉及)。@
fx:include
评论