我正在尝试使用 Maven 和 Visual Studio Code 使我的 JavaFx 应用程序可执行。
在这个主题上花了一些时间后,我发现了一些提到 jlink 的帖子。
我是打包 Java/JavaFX 应用程序的新手,所以我尝试了一下。
目前,我至少可以执行包的启动器。
但是启动应用程序后,立即抛出 NullPointerException: 无法调用“Object.toString()”,因为“java.lang.Class.getResource(String)”的返回值为 null。
为了设置视图组件的样式,我创建了一些 .css 文件并将它们放入 /style 目录中。根据示例 JavaFx 应用程序,我将此目录放置在 Maven 创建的 /resources 目录中。以类似的方式,我继续处理我的声音和图像文件。
在这里您可以看到我的目录结构的摘录。
|
|--src/main
| |
| |-- java
| | | ...
| |
| |-- resources
| |
| |-- img
| | | ...
| |
| |-- style
| | | ...
| |
| |-- sound
| | ...
|
|-- target
|
|-- classes
| | ...
| |
| |-- img
| | | ...
| |
| |-- style
| | | ...
| |
| |-- sound
| | | ...
|
|-- ...
|
|-- app
|
|-- bin
|-- ...
现在我正在尝试从我的应用程序中访问我的资源。
这是我的第一个方法。从 VSCode 运行时效果很好。
public static final String PATH_TO_STYLESHEET = App.class.getResource("/style").toString();
public static final String PATH_TO_IMG = App.class.getResource("/img").toString();
public static final String PATH_TO_SOUNDS = App.class.getResource("/sounds").toString();
但是运行 jlink 后,我的应用程序崩溃了,显示前面提到的 NullPointerException。
这是我的 pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.openjfx</groupId>
<artifactId>App</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>19</maven.compiler.release>
<javafx.version>19</javafx.version>
<javafx.maven.plugin.version>0.0.8</javafx.maven.plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>${maven.compiler.release}</release>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>${javafx.maven.plugin.version}</version>
<configuration>
<release>${maven.compiler.release}</release>
<jlinkImageName>App</jlinkImageName>
<launcher>launcher</launcher>
<mainClass>com.test.App</mainClass>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
</project>
这是我一直用于创建包的命令。
mvn javafx:jlink -f pom.xml
有谁知道运行 jlink 后如何获取样式表、图像和声音的路径?这条路绝对够用了。我不需要文件本身。
是否可以选择将资源复制到特定位置?
您有如下代码:
public static final String PATH_TO_IMG = App.class.getResource("/img").toString();
这是试图获取资源
"/img"
。但根据你的问题,这本身并不是一个资源,而是一个目录(即一个包)。问题似乎是当 Class#getResource(String)
参数表示目录时 String
的行为不一致。当您的代码not在JRT图像中时,对
#getResource(String)
的调用将返回URL
;当您的代码is打包在JRT映像中时,相同的调用将返回
null
,尽管该目录存在。我不知道这种行为是一个错误还是只是未定义。一件有趣的事情是 显然能够查找目录: 查找资源,返回模块中资源的 URI。
如果模块读取器可以确定该名称定位目录,则生成的 URI 将以斜杠结尾 ()。请注意,如果您查询)。
'/'
至少对我来说,这表明您尝试做的事情应该是可能的。但当模块打包在 JRT 映像中时,即使该方法也会失败(通过返回空的
Optional
ModuleReader#list()
方法,当模块 not位于 JRT 映像中时,它将包含目录,但当模块 is 位于 JRT 映像中时,不会包含相同的目录。 示例 我在这个答案的末尾放了一个最小的例子来演示这个问题。
解决方案
PATH_TO_IMG
Image image = new Image(PATH_TO_IMG + "foo.png");
这避免了到处打电话给SomeClass.class.getResource("...").toString()
。如果这是你的目标,那么我至少可以想到一个解决方案。更改常量以简单地引用资源根
。例如:
public static final String IMG_ROOT = "/img";
然后创建一个实用方法来解析资源:public static String getImagePath(String name) {
var resource = IMG_ROOT + "/" + name;
var url = App.class.getResource(resource);
if (url == null) {
throw new RuntimeException("could not find resource: " + resource);
}
return url.toString();
}
然后您可以像这样使用该实用方法:
Image image = new Image(getImagePath("foo.png"));
可能的替代方案另一种选择可能是使用 JRT
FileSystem
FileSystem jrtFs = FileSystems.getFileSystem(URI.create("jrt:/"));
Path path = jrtFs.getPath("modules", "<module-name>", "img");
// Note: Doesn't seem to include the trailing '/'
String pathToImg = path.toUri().toString();
尽管您必须检测您的代码是否在 JRT 映像中。
最小示例
Maven 3.8.6
| pom.xml
|
\---src
\---main
+---java
| | module-info.java
| |
| \---sample
| Main.java
|
\---resources
\---data
file.txt
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sample</groupId>
<artifactId>app</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>19</maven.compiler.release>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jlink-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<launcher>app=app/sample.Main</launcher>
</configuration>
</plugin>
</plugins>
</build>
</project>
module app {}
package sample;
public class Main {
public static void main(String[] args) {
var modRef = Main.class.getModule()
.getLayer()
.configuration()
.findModule(Main.class.getModule().getName())
.orElseThrow()
.reference();
System.out.printf("Module Location = %s%n%n", modRef.location().orElseThrow());
var dataUrl = Main.class.getResource("/data");
var fileUrl = Main.class.getResource("/data/file.txt");
System.out.printf("Data URL = %s%nFile URL = %s%n%n", dataUrl, fileUrl);
}
}
mvn compile jar:jar
mvn jlink:jlink
mvn compile jar:jar jlink:jlink
jlink
任务失败。
输出这是不同包装的不同输出:
...> java -p target\classes -m app/sample.Main
Module Location = file:///C:/Users/***/Desktop/jlink-tests/target/classes/
Data URL = file:/C:/Users/***/Desktop/jlink-tests/target/classes/data/
File URL = file:/C:/Users/***/Desktop/jlink-tests/target/classes/data/file.txt
...> java -p target\app-1.0-SNAPSHOT.jar -m app/sample.Main
Module Location = file:///C:/Users/***/Desktop/jlink-tests/target/app-1.0-SNAPSHOT.jar
Data URL = jar:file:///C:/Users/***/Desktop/jlink-tests/target/app-1.0-SNAPSHOT.jar!/data/
File URL = jar:file:///C:/Users/***/Desktop/jlink-tests/target/app-1.0-SNAPSHOT.jar!/data/file.txt
...> .\target\maven-jlink\default\bin\app
Module Location = jrt:/app
Data URL = null
File URL = jrt:/app/data/file.txt
getResource("/data/file.txt")
getResource("/data")
的调用对于 JRT 打包版本不起作用。您需要将 JRT 注册为有效的 url 模式,那么无论如何它都可以全局使用
public class JrtUrlHandler
extends URLStreamHandlerProvider
{
@Override
public URLStreamHandler createURLStreamHandler(String protocol)
{
if ("jrt".equals(protocol))
{
return new URLStreamHandler()
{
@Override
protected URLConnection openConnection(URL u) throws IOException
{
return new JrtUrlConnection(u);
}
};
}
return null;
}
}
然后连接
public class JrtUrlConnection
extends URLConnection
{
private Path path;
/**
* Cleans up incorrectly found jar:jrt urls and returns a viable URI
* @param url
*/
protected JrtUrlConnection(URL url)
{
super(url);
URI uri = URI.create(url.toString()
.replace("jar:jrt:/", "jrt:/")
.replace("!", ""));
path = Path.of(uri);
}
@Override
public void connect() throws IOException
{
}
@Override
public int getContentLength()
{
return (int) path.toFile()
.length();
}
@Override
public long getContentLengthLong()
{
return path.toFile()
.length();
}
@Override
public long getDate()
{
return path.toFile()
.lastModified();
}
@Override
public long getLastModified()
{
return path.toFile()
.lastModified();
}
@Override
public InputStream getInputStream() throws IOException
{
return Files.newInputStream(path);
}
@Override
public OutputStream getOutputStream() throws IOException
{
return Files.newOutputStream(path);
}
}
在模块信息中为 java.net.spi.URLStreamHandlerProvider 注册服务加载器
provides java.net.spi.URLStreamHandlerProvider with JrtUrlHandler;
对于测试类支持,还要在 META-INF/services 中创建服务加载器文件所有标准操作都可以干净地工作,class.getReosurceStream 等,
适用于 JDK 9 到 23,尚未损坏