答案简短版:
-
使用
getClass().getResource(...)
或 SomeOtherClass.class.getResource(...)
创建 URL
资源
-
将绝对路径(以 开头
/
)或相对路径(不以 开头 /
)传递给该 getResource(...)
方法。路径是 包含资源的 包 .
替换为 /
.
-
不要
..
在资源路径中使用。如果应用程序被打包为 jar 文件,则此方法无效。如果资源不在同一个包中或类的子包中,请使用绝对路径。
-
对于 FXML 文件,
URL
直接传递给 FXMLLoader
.
-
对于图像和样式表,调用
toExternalF或m()
来 URL
生成 String
传递给 Image
or ImageView
构造函数,或添加到 stylesheets
列表中。
-
构建 文件夹(或 jar 文件) 的内容 源 文件夹。
-
获取资源时
src
放置 总是 错误的。该 src
目录仅在开发和构建时可用,而不是在部署和运行时可用。
完整答案
内容
-
本答案的范围
-
资源在运行时加载
-
JavaFX 使用 URL 加载资源
-
资源流
-
资源名称规则
-
使用以下方式创建资源 URL
getClass().getResource(...)
-
组织代码和资源
-
Maven(及类似)标准布局
-
故障排除
本答案的范围
请注意,此答案 仅 涉及加载作为应用程序一部分并与其捆绑在一起的资源(例如 FXML 文件、图像和样式表)。因此,例如,加载用户从运行应用程序的机器上的文件系统中选择的图像将需要不同的技术,本文未涵盖这些技术。
资源在运行时加载
关于加载资源,首先要了解的是,它们当然是在运行时加载的。通常,在开发期间,应用程序从文件系统运行:也就是说,运行应用程序所需的类文件和资源是文件系统上的单独文件。但是,一旦构建了应用程序,它通常会从 jar 文件执行。在这种情况下,FXML 文件、样式表和图像等资源不再是文件系统上的单独文件,而是 jar 文件中的条目。因此:
代码无法使用 File
, FileInputStream
或 file:
URL 加载资源
JavaFX 使用 URL 加载资源
JavaFX 使用 URL 加载 FXML、图像和 CSS 样式表。
明确 FXMLLoader
期望将一个 java.net.URL
对象传递给它(传递给方法 static
FXMLLoader.load(...)
、 FXMLLoader
构造函数或方法 setLocation()
)。
和 Image
都 Scene.getStylesheets().add(...)
要求 String
表示 URL。如果传递的 URL 没有方案,则它们将相对于类路径进行解释。可以 URL
通过 toExternalForm()
调用 URL
.
为资源创建正确 URL 的推荐机制是使用 Class.getResource(...)
,它在适当的实例上调用 Class
(给出当前对象的类)或 来 getClass()
获取此类实例 ClassName.class
。该 Class.getResource(...)
方法采用 String
表示资源名称的 。
资源流
如果您需要将资源作为流,则可以使用 getClass().getResourceAsStream(...)
或 SomeOtherClass.class.getResourceAsStream(...)
为资源数据创建流。但是,对于 JavaFX 中的大多数工作,您不需要这样做,因为 JavaFX API 主要使用 URL 作为输入,而不是流。有些 API(例如 Image 构造函数和 FXMLLoader)可以同时使用 URL 和流。对于此类 API,通常最好使用基于 URL 的 API 形式,而不是基于流的 API 形式。这允许 FXMLLoader 等进程找到包含资源的相对 URL,而如果仅提供原始 InputStream,则无法做到这一点。
资源名称规则
-
资源名称是以
/
- 分隔的路径名。每个组件代表一个包或子包名称组件。
-
资源名称区分大小写。
-
资源名称中的各个组件 必须是有效的 Java 标识符
最后一点有一个重要的结论:
.
并且 ..
不是有效的 Java 标识符,因此 不能在资源名称中使用 .
当应用程序从文件系统运行时,这些可能确实有效,尽管这实际上更像是实现的意外 getResource()
。当应用程序捆绑为 jar 文件时,它们将失败。
类似地,如果您在不区分仅大小写不同的文件名的操作系统上运行,那么在资源名称中使用错误的大小写可能会在从文件系统运行时起作用,但从 jar 文件运行时会失败。
以 开头的资源名称 /
是 绝对的 :换句话说,它们是相对于类路径进行解释的。没有开头的资源名称 /
是相对于调用的类进行解释的 getResource()
。
对此的一个轻微变化是使用 getClass().getClassLoader().getResource(...)
。提供给 的路径 ClassLoader.getResource(...)
不能 以 开头 /
始终 是 绝对路径,即相对于类路径。还应注意,在模块化应用程序中,使用 访问资源 ClassLoader.getResource()
在某些情况下受强封装规则的约束,此外,必须无条件打开包含资源的包。有关详细信息,请参阅 文档 。
使用以下方式创建资源 URL getClass().getResource()
要创建资源 URL,请使用 someClass.getResource(...)
。通常, someClass
表示当前对象的类,并使用 获取 getClass()
。但是,情况不一定如此,如下一节所述。
-
第15页
// 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());
-
p16
URL cssURL = getClass().getResource("/org/jamesd/examples/css/style.css"); scene.getStylesheets().add(cssURL.toExternalForm());
p17
组织代码和资源
我建议将代码和资源组织到与它们关联的 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 和其他依赖项管理和构建工具建议使用 源 文件夹布局,其中资源与 Java 源文件分开,如 Maven 标准目录布局 。上一个示例的 Maven 布局版本如下所示:
了解如何构建这个组件对于组装应用程序非常重要:
-
*.java
源 文件夹 中的文件 src/main/java
被编译为类文件,并部署到构建文件夹或jar文件中。
-
将 资源 文件夹
src/main/resources
中的资源 复制 到构建文件夹或者jar文件中。
在此示例中,由于资源位于与定义源代码的包的子包相对应的文件夹中,因此生成的构建(在 Maven 中默认位于 target/classes
)由单一结构组成。
请注意, src/main/java
和 src/main/resources
被视为构建中相应结构的根,因此只有它们的内容(而不是文件夹本身)才是构建的一部分。换句话说, resources
运行时没有可用的文件夹。构建结构显示在下面的“故障排除”部分中。
请注意,在这种情况下,IDE (Eclipse) 显示 src/main/java
源文件夹与文件夹不同 src/main/resources
;在第一种情况下,它显示 包 ,但对于资源文件夹,它显示 文件夹 。确保您知道您是否正在 IDE 中创建包(其名称以 .
- 分隔)或文件夹(其名称不得包含 .
或任何其他在 Java 标识符中无效的字符)。
如果您正在使用 Maven,并且为了便于维护,您决定将 .fxml 文件放在引用它们的 .java 文件旁边(而不是严格遵循 Maven 标准目录布局 在 pom.xml 文件中 包含 类似以下内容
<build>
...
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.fxml</include>
<include>**/*.css</include>
</includes>
</resource>
...
</build>
如果您这样做,那么您可以使用一种方法, FXMLLoader.load(getClass().getResource("MyFile.fxml"))
让您的类从包含其自己的.class 文件的目录中加载.fxml 资源。
故障排除
如果出现意外错误,请首先检查以下内容:
-
确保您没有对资源使用无效的名称。这包括
.
在资源路径中 ..
或
-
确保在需要的地方使用相对路径,在需要的地方使用绝对路径。对于
Class.getResource(...)
路径,如果以 开头 /
,则为绝对路径,否则为相对路径。对于 ClassLoader.getResource(...)
,路径始终是绝对路径,并且 不能 以 开头 /
.
-
请记住,绝对路径是相对于 classpath 。通常,classpath 的根是 IDE 中所有源和资源文件夹的并集。
如果所有这些看起来都正确,但您仍然看到错误,请检查 构建 或部署文件夹。此文件夹的确切位置因 IDE 和构建工具而异。如果您使用的是 Maven,则默认情况下为 target/classes
。其他构建工具和 IDE 将部署到名为 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 coding 的教程:
Eden 编码教程的优点在于它很全面。除了涵盖此问题中有关从 Java 代码查找的信息之外。Eden 教程涵盖了诸如定位在 CSS 中编码为 URL 的资源,或使用说明 @
符或 fx:include
元素在 FXML 中引用资源等主题(这些主题目前未在此答案中直接涵盖)。