简短的回答:
- 使用
getClass().getResource(...)
或 SomeOtherClass.class.getResource(...)
创建指向资源的 URL
- 将绝对路径(以前导的
/
)或相对路径(不带前导的 /
)传递给 getResource(...)
方法。路径是包含资源的包,其中 .
替换为 /
。
- 不要使用
..
在资源路径中。如果应用程序被打包为 jar 文件,它将无法工作。如果资源不在类的相同包或子包中,请使用绝对路径。
- 对于 FXML 文件,直接将
URL
传递给 FXMLLoader
。
- 对于图像和样式表,在
URL
上调用 toExternalForm()
以生成要传递给 Image
或 ImageView
构造函数的 String
,或者添加到 stylesheets
列表中。
- 要排除故障,检查您的 build 文件夹(或 jar 文件)中的内容,而不是您的 source 文件夹。
- 在获取资源时将
src
放入路径中总是错误的。 src
目录仅在开发和构建时可用,而不是在部署和运行时。
完整答案
目录
- 本答案的范围
- 资源在运行时加载
- JavaFX使用URL来加载资源
- 资源名称的规则
- 使用
getClass().getResource(...)
创建资源URL
- 组织代码和资源
- 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(...)
,都需要代表URL的String
。如果传递的URL没有方案,它们将相对于类路径进行解释。这些字符串可以通过在URL
上调用toExternalForm()
以强健的方式创建。
为资源创建正确的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()
获取。但是,这不一定是这种情况,如下一节所述。
If the resource is in the same package as the current class, or in a subpackage of that class, use a relative path to the resource:
URL fxmlURL = getClass().getResource("MyFile.fxml");
Parent root = FXMLLoader.load(fxmlURL);
URL fxmlURL2 = getClass().getResource("fxml/MyFile.fxml");
Parent root2 = FXMLLoader.load(fxmlURL2);
URL imageURL = getClass().getResource("myimages/image.png");
Image image = new Image(imageURL.toExternalForm());
If the resource is in a package that is not a subpackage of the current class, use an absolute path. For example, if the current class is in the package org.jamesd.examples.view
, and we need to load a CSS file style.css
which is in the package org.jamesd.examples.css
, we have to use an absolute path:
URL cssURL = getClass().getResource("/org/jamesd/examples/css/style.css");
scene.getStylesheets().add(cssURL.toExternalForm());
It's worth re-emphasizing for this example that the path "../css/style.css"
does not contain valid Java resource names, and will not work if the application is bundled as a jar file.
代码和资源的组织
我建议按照与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布局版本如下:
重要的是要了解如何构建应用程序:
- 位于
src/main/java
源文件夹中的*.java
文件被编译为类文件,并部署到构建文件夹或jar文件中。
- 位于
src/main/resources
资源文件夹中的资源被复制到构建文件夹或jar文件中。
在此示例中,由于资源位于与定义源代码的包的子包相对应的文件夹中,因此生成的构建(默认情况下使用Maven在
target/classes
中)由单个结构组成。
请注意,
src/main/java
和
src/main/resources
都被视为相应结构在构建中的根目录,因此只有它们的内容而不是文件夹本身是构建的一部分。换句话说,在运行时没有可用的
resources
文件夹。构建结构在“故障排除”部分中显示。
请注意,在这种情况下(Eclipse),IDE以不同的方式显示
src/main/java
源文件夹和
src/main/resources
文件夹。对于第一种情况,它显示
包,但对于资源文件夹,它显示
文件夹。确保您知道您的IDE是否正在创建包(其名称以
。
分隔)或文件夹(其名称不得包含
。
或任何其他Java标识符中无效的字符)。
如果您正在使用Maven,并决定为了便于维护,您更愿意将.fxml文件放在引用它们的.java文件旁边(而不是严格遵循
Maven Standard Directory Layout),则可以这样做。只需在pom.xml文件中包含
类似以下内容,告诉Maven将这些文件复制到与生成的类文件相同的文件夹中,该类文件是从这些源文件生成的:
<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(...)
,路径始终是绝对的,并且
不能以
/
开头。
- 请记住,绝对路径是相对于
类路径定义的。通常,类路径的根是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编码的教程:
Eden编码教程的好处在于它是全面的。除了涵盖本问题中关于从Java代码进行查找的信息外,Eden教程还涵盖了其他主题,例如在CSS中作为url编码的资源的定位,或者使用@
指定符或fx:include
元素在FXML中引用资源(这些主题目前不在本回答中直接涵盖)。