作为IntelliJ Maven项目构建的JavaFX应用程序打包为JAR时如何加载资源?
How to load resources when JavaFX app built as IntelliJ Maven project is packaged as JAR?
问题
当应用程序被打包为 Jar 文件时,我应该怎么做才能在我的 JavaFX 应用程序中加载资源?
背景
该应用程序是IntelliJ IDEA 中的Maven 项目。 运行 直接来自 IntelliJ 的应用程序适用于下面描述的所有情况,但我从项目构建的 jar 文件大多无法加载资源(图像和字体)。
项目目录树
application
├── .idea
├── out
├── src
| ├── main
| | ├── java
| | | ├── META-INF
| | | └── Main.java
| | └── resources
| | ├── images
| | | ├── img1.jpg
| | | └── img2.png
| | ├── fonts
| | | └── font.ttf
| | └── stylesheet.css
| └── test
├── target
├── application.iml
└── pom.xml
加载图像 工作方式如下:
Image img1 = new Image(this.getClass().getResource("images/img1.jpg").toString());
Image img2 = new Image(this.getClass().getResource("images/img2.png"));
但是因为我实际上想加载图像文件夹中的所有图像而不对它们的名称进行硬编码,所以我一直在这样做:
File dir = new File(this.getClass().getResource("images").getFile());
List<File> imageFiles = new ArrayList<>(Arrays.asList(dir.listFiles()));
List<Image> images = new ArrayList<>();
for (int i = 0; i < imageFiles.size(); i++) {
images.add(new Image("images/" + imageFiles.get(i).getName()));
}
当应用程序已打包到 jar 文件中并导致第 2 行出现 NullPointerException 时,这不起作用。事实证明 dir.listFiles()
returns null 并且 dir.exists()
returns 错误。
加载字体 我试过两种方法。像这样 stylesheet.css
:
@font-face {
font-family: 'Font';
src: url('fonts/font.ttf');
}
或者像这样作为Main的启动方法的第一行:
Font.loadFont(getClass().getResourceAsStream("fonts/font.ttf"), 20);
无论哪种情况,我都是通过样式表应用字体。当 运行 来自 IntelliJ 的应用程序时,这两种情况都有效,但当 运行 jar 时,这两种情况都无效。第一种方法打印错误信息
Jun 14, 2018 4:21:47 AM com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged
INFO: Could not load @font-face font [jar:file:/D:/path/in/file/system/java/application/out/artifacts/application_jar/application.jar!/fonts/font.ttf]
而第二种方法毫无提示地失败了。
我做了什么和其他信息
我正在通过 Build -> Build Artifacts...
在 IntelliJ 中构建 jar。 resources
目录在 IntelliJ 中标记为 Resources Root,这是我的 pom.xml.
生成的jar文件确实包含了我认为应该有的所有资源。也就是说,它具有以下目录树:
application.jar
├── images
| ├── img1.jpg
| └── img2.png
├── fonts
| └── font.ttf
├── META-INF
├── Main.class
└── stylesheet.css
我在做这个 post 之前参考过的资源是 Apache Maven Archiver, IntelliJ Working With Artifacts, Whosebug NullPointerException When..., Whosebug JavaFX and maven... 和几个类似的问题。他们中的许多人解决了 NullPointerException: Location is required
问题,这似乎是在从 xml 加载 JavaFX 布局时发生的,我没有这样做,也不是我得到的错误。
请注意,我以前几乎没有使用 Maven 的经验,只有文件流、class 加载程序等的基本概念以及一些 JavaFX 经验。
这个问题有两个答案。
加载字体只是我的错字,我在路径名中使用了错误的大小写。有趣的是,这个错字只会在 运行 jar 文件时导致字体无法加载,而当 运行 来自 IntelliJ 的应用程序时,这无关紧要。因此,您可以像这样从 jar 中加载字体:
Font.loadFont(Memory.class.getResourceAsStream("fonts/Font.ttf"), 20);
从 jar 中加载文件,而不知道文件的确切名称,如果没有解决方法,这似乎是不可能的。您必须获得对 jar 文件本身的引用并枚举其条目。这在他的评论中链接到 this question that Adi 的答案中有所描述。
因此,我能够像这样加载图像:
List<String> imagePaths = getPaths("images");
List<Image> imagePool = new ArrayList<>();
for (int i = 0; i < imagePaths.size(); i++) {
imagePool.add(new Image(imagePaths.get(i)));
}
其中 getPaths
是
/** Returns the paths to all files in a folder. Useful for loading many files without knowing their names.
* Importantly, this function will work whether or not the application is run from a jar or not.
* However it might not work if the jar is not run locally.
*
* @param folderPath The relative path to the folder with no ending slash.
* @return A List of path names to all files in that folder.
* @throws IOException
*/
public static List<String> getPaths(String folderPath) throws IOException {
final File jarFile = new File(Memory.class.getProtectionDomain().getCodeSource().getLocation().getPath());
List<String> filePaths = new ArrayList<>();
if(jarFile.isFile()) { // Run with JAR file
final JarFile jar = new JarFile(jarFile);
final Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
while(entries.hasMoreElements()) {
final String name = entries.nextElement().getName();
if (name.startsWith(folderPath + "/")) { //filter according to the folderPath
filePaths.add(name);
}
}
jar.close();
} else { // Run with IDE
final URL url = Memory.class.getResource("/" + folderPath);
if (url != null) {
try {
final File apps = new File(url.toURI());
for (File app : apps.listFiles()) {
filePaths.add(folderPath + "/" + app.getName());
}
} catch (URISyntaxException ex) {
// never happens
}
}
}
return filePaths;
}
与 Adi 链接到的问题的答案仅略有不同。
问题
当应用程序被打包为 Jar 文件时,我应该怎么做才能在我的 JavaFX 应用程序中加载资源?
背景
该应用程序是IntelliJ IDEA 中的Maven 项目。 运行 直接来自 IntelliJ 的应用程序适用于下面描述的所有情况,但我从项目构建的 jar 文件大多无法加载资源(图像和字体)。
项目目录树
application
├── .idea
├── out
├── src
| ├── main
| | ├── java
| | | ├── META-INF
| | | └── Main.java
| | └── resources
| | ├── images
| | | ├── img1.jpg
| | | └── img2.png
| | ├── fonts
| | | └── font.ttf
| | └── stylesheet.css
| └── test
├── target
├── application.iml
└── pom.xml
加载图像 工作方式如下:
Image img1 = new Image(this.getClass().getResource("images/img1.jpg").toString());
Image img2 = new Image(this.getClass().getResource("images/img2.png"));
但是因为我实际上想加载图像文件夹中的所有图像而不对它们的名称进行硬编码,所以我一直在这样做:
File dir = new File(this.getClass().getResource("images").getFile());
List<File> imageFiles = new ArrayList<>(Arrays.asList(dir.listFiles()));
List<Image> images = new ArrayList<>();
for (int i = 0; i < imageFiles.size(); i++) {
images.add(new Image("images/" + imageFiles.get(i).getName()));
}
当应用程序已打包到 jar 文件中并导致第 2 行出现 NullPointerException 时,这不起作用。事实证明 dir.listFiles()
returns null 并且 dir.exists()
returns 错误。
加载字体 我试过两种方法。像这样 stylesheet.css
:
@font-face {
font-family: 'Font';
src: url('fonts/font.ttf');
}
或者像这样作为Main的启动方法的第一行:
Font.loadFont(getClass().getResourceAsStream("fonts/font.ttf"), 20);
无论哪种情况,我都是通过样式表应用字体。当 运行 来自 IntelliJ 的应用程序时,这两种情况都有效,但当 运行 jar 时,这两种情况都无效。第一种方法打印错误信息
Jun 14, 2018 4:21:47 AM com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged
INFO: Could not load @font-face font [jar:file:/D:/path/in/file/system/java/application/out/artifacts/application_jar/application.jar!/fonts/font.ttf]
而第二种方法毫无提示地失败了。
我做了什么和其他信息
我正在通过 Build -> Build Artifacts...
在 IntelliJ 中构建 jar。 resources
目录在 IntelliJ 中标记为 Resources Root,这是我的 pom.xml.
生成的jar文件确实包含了我认为应该有的所有资源。也就是说,它具有以下目录树:
application.jar
├── images
| ├── img1.jpg
| └── img2.png
├── fonts
| └── font.ttf
├── META-INF
├── Main.class
└── stylesheet.css
我在做这个 post 之前参考过的资源是 Apache Maven Archiver, IntelliJ Working With Artifacts, Whosebug NullPointerException When..., Whosebug JavaFX and maven... 和几个类似的问题。他们中的许多人解决了 NullPointerException: Location is required
问题,这似乎是在从 xml 加载 JavaFX 布局时发生的,我没有这样做,也不是我得到的错误。
请注意,我以前几乎没有使用 Maven 的经验,只有文件流、class 加载程序等的基本概念以及一些 JavaFX 经验。
这个问题有两个答案。
加载字体只是我的错字,我在路径名中使用了错误的大小写。有趣的是,这个错字只会在 运行 jar 文件时导致字体无法加载,而当 运行 来自 IntelliJ 的应用程序时,这无关紧要。因此,您可以像这样从 jar 中加载字体:
Font.loadFont(Memory.class.getResourceAsStream("fonts/Font.ttf"), 20);
从 jar 中加载文件,而不知道文件的确切名称,如果没有解决方法,这似乎是不可能的。您必须获得对 jar 文件本身的引用并枚举其条目。这在他的评论中链接到 this question that Adi 的答案中有所描述。
因此,我能够像这样加载图像:
List<String> imagePaths = getPaths("images");
List<Image> imagePool = new ArrayList<>();
for (int i = 0; i < imagePaths.size(); i++) {
imagePool.add(new Image(imagePaths.get(i)));
}
其中 getPaths
是
/** Returns the paths to all files in a folder. Useful for loading many files without knowing their names.
* Importantly, this function will work whether or not the application is run from a jar or not.
* However it might not work if the jar is not run locally.
*
* @param folderPath The relative path to the folder with no ending slash.
* @return A List of path names to all files in that folder.
* @throws IOException
*/
public static List<String> getPaths(String folderPath) throws IOException {
final File jarFile = new File(Memory.class.getProtectionDomain().getCodeSource().getLocation().getPath());
List<String> filePaths = new ArrayList<>();
if(jarFile.isFile()) { // Run with JAR file
final JarFile jar = new JarFile(jarFile);
final Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
while(entries.hasMoreElements()) {
final String name = entries.nextElement().getName();
if (name.startsWith(folderPath + "/")) { //filter according to the folderPath
filePaths.add(name);
}
}
jar.close();
} else { // Run with IDE
final URL url = Memory.class.getResource("/" + folderPath);
if (url != null) {
try {
final File apps = new File(url.toURI());
for (File app : apps.listFiles()) {
filePaths.add(folderPath + "/" + app.getName());
}
} catch (URISyntaxException ex) {
// never happens
}
}
}
return filePaths;
}
与 Adi 链接到的问题的答案仅略有不同。