VSCode:缺少 JavaFX 运行时组件

VSCode: JavaFX runtime components are missing

我有一个 Maven 项目,我想将其导出到 运行nable .jar 文件中。

在 VSCode 中,项目运行没有问题。导出的项目包括一个 .jar 文件和一个包含所有 javaFX 文件的 libs 文件夹:

Documents/prosjekt/target
├── classes
├── generated-sources
├── generated-test-sources
├── libs
│   ├── apiguardian-api-1.1.0.jar
│   ├── javafx-base-18.0.1-mac-aarch64.jar
│   ├── javafx-base-18.0.1.jar
│   ├── javafx-controls-18.0.1-mac-aarch64.jar
│   ├── javafx-controls-18.0.1.jar
│   ├── javafx-fxml-18.0.1-mac-aarch64.jar
│   ├── javafx-fxml-18.0.1.jar
│   ├── javafx-graphics-18.0.1-mac-aarch64.jar
│   ├── javafx-graphics-18.0.1.jar
│   ├── javafx-media-18.0.1-mac-aarch64.jar
│   ├── javafx-media-18.0.1.jar
│   ├── junit-jupiter-api-5.7.0.jar
│   ├── junit-jupiter-engine-5.7.0.jar
│   ├── junit-platform-commons-1.7.0.jar
│   ├── junit-platform-engine-1.7.0.jar
│   └── opentest4j-1.2.0.jar
├── maven-archiver
├── maven-status
├── surefire-reports
├── prosjekt_boilerplate-1.0-SNAPSHOT.jar
└── test-classes

当我运行以下命令时出现问题:

❯ java -jar /Users/user/Documents/prosjekt/target/prosjekt_boilerplate-1.0-SNAPSHOT.jar 
Error: JavaFX runtime components are missing, and are required to run this application

我不明白为什么会出现此错误,因为所有 JavaFX 文件都位于 libs 文件夹中并且可见。


我目前使用 Java 来自 SDKman 的 Temurin 18.0.1。

❯ java -version
openjdk version "18.0.1" 2022-04-19
OpenJDK Runtime Environment Temurin-18.0.1+10 (build 18.0.1+10)
OpenJDK 64-Bit Server VM Temurin-18.0.1+10 (build 18.0.1+10, mixed mode)

Pom.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>project</groupId>
    <artifactId>prosjekt_boilerplate</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
 
    <name>prosjekt_boilerplate</name>
    

    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>18.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-media</artifactId>
            <version>18.0.1</version>
        </dependency>

        <!-- JUnit 5 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.7.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>17</release>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>
                <configuration>
                    <argLine>--enable-preview</argLine>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-failsafe-plugin</artifactId>
                    <version>3.0.0-M5</version>
                    <configuration>
                        <argLine>--enable-preview</argLine>
                    </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>
                                ${project.build.directory}/libs
                            </outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>libs/</classpathPrefix>
                            <mainClass>
                                asteroids.AsteroidsApp
                            </mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

编辑

我尝试下载 JavaFX SDK 并运行执行以下命令:

❯ java -jar --module-path /Users/user/Downloads/javafx-sdk-18.0.1/lib \
--add-modules=javafx.controls,javafx.media,javafx.fxml,javafx.graphics \
/Users/user/Documents/prosjekt/target/prosjekt_boilerplate-1.0-SNAPSHOT.jar

成功了!但它仍然没有回答我原来的问题。

虽然出于解释目的这是一个很长的答案,但修复实际上非常简单。

唯一需要做的就是将带有模块的 jar 放在模块路径而不是 class路径上。


回答您的问题以解释您原来的方法失败的原因。

The problem occurs when I run the following command:

java -jar /Users/user/Documents/prosjekt/target/prosjekt_boilerplate-1.0-SNAPSHOT.jar

Error: JavaFX runtime components are missing, and are required to run this application

我不明白为什么会出现此错误,因为所有 JavaFX 文件都位于 libs 文件夹中并且可见。

您收到此错误的原因是您 运行 将所有代码从 class 路径而不是模块路径中删除。

现代 JavaFX 版本未设计或不支持来自 class 路径的 运行。它们应该来自模块路径 运行。

你做了以下事情,这将使程序只在class路径上执行:

  1. 使用您的应用程序创建了一个 jar。
  2. 在指定执行的 jar 中创建了一个清单 class。
  3. 向清单添加了一个库路径。

背景信息

您的应用程序 class 不需要是模块化的,其他(non-JavaFX)库可以从 class 路径而不是模块路径执行当放置在 class 路径上时功能正常,但 JavaFX 库将失败。

解决方案是至少将 JavaFX classes 放在模块路径上。理想情况下,您还可以使您的应用程序模块化(为其定义一个 module-info.java 文件)并在您的应用程序中仅使用 non-automatic 模块化 jar(那些也定义了 module-info.java 的)。但理想的不是必须的(除非你想使用jlink打包你的应用程序),只需要在模块路径上有JavaFX classes。

您不能(当前)通过 jar 清单将其他 jar 或文件夹添加到模块路径,就像您目前可以使用 class 路径一样。有关这方面的信息,请参阅此问题的答案(以及对此答案的评论):

  • Java modules and the JAR MANIFEST Class-Path attribute

相反,您需要在命令行中指定您添加的模块的位置(或者依靠由 jlink 创建的 JRE 映像 pre-bundles 所需的模块)。

此外,如果您的应用程序是模块化的,您应该使用 -m 开关来指定主 class 由它所在的模块限定,并且您的应用程序 jar 也应该在模块路径上。如果您的应用程序不是模块化的,那么您仍然可以使用 -jar 说明符并将主要 class 名称编码在 jar 清单中,而不是作为 command-line 开关。

例子

因此,假设您将应用程序作为一个 zip 文件分发,其中包含您的应用程序 jar 和依赖库,位于 sub-folder 名为 lib 中。

对于这些示例,应用程序 jar 是一个普通的薄 jar(它不是一个带有阴影或包含 JavaFX 运行时间代码的胖 jar)。

对于 non-modular 应用程序,这在 eden coding 中进行了讨论。用户解压你的包后,假设主要的 class 名称被编码在清单中,那么应用程序可以是 运行 via:

java -jar myJar.jar --module-path lib --add-modules javafx.controls,javafx.fxml

如果您使用其他 JavaFX 模块,例如 javafx-media,它们不是传递依赖项,那么也包括这些。

如果您的应用程序是模块化的,那么您不需要在 jar 清单中编码额外信息,但您需要在 jar 文件中包含 module-info.java。执行此操作时,最好将您的 jar 与您正在使用的其余模块放在同一个 lib 目录中,这样您就不需要将它单独添加到模块路径中。完成此操作后,您可以 运行 像这样的应用程序(此示例来自 jenkov tutorial on Java modules):

 java --module-path lib -m com.jenkov.mymodule/com.jenkov.mymodule.Main

您可以使用jar 命令设置一个主要class 到运行 模块化jar 文件(在jenkov 教程中也有演示)。然后你可以 运行 该模块化 jar 而无需显式提供主要 class,但指定 jar 文件和模块路径(JavaFX 应用程序仍然需要模块路径):

 jar -c --file=lib/com-jenkov-mymodule.jar --main-class=com.jenkov.mymodule.Main -C out/com.jenkov.mymodule .
 java --module-path lib -jar lib/com-jenkov-javafx.jar

我不知道 jar 命令究竟做了什么将执行命令添加到 jar 文件中,但我不认为它使用清单来执行此操作。目前,我认为 maven jar 命令无法正确执行此操作(我认为它只是将信息放入清单中,而不是将模块化主要 class 执行信息放入 jar)。

总而言之,使用 Java 模块和使用 class 路径是不同且令人困惑的。 . . :-)

破解

有(至少 Java 17)一个 hack 来创建一个单独的启动器 class 绕过 运行 时间检查以允许启动 JavaFX 框架来自模块路径而不是 class 路径,但它不受支持。我不推荐,这里就不多说了。

进一步的建议

因为命令行开关容易出错或打错,所以bst 在部署 JavaFX 应用程序时提供一个 platform-specific 执行脚本(unix/mac shell 脚本或 windows 批处理文件),用户可以更轻松地使用它来执行应用程序。

还可以考虑使用 Java 运行 时间分发,通过压缩的 jlink 图像或 jpackage 创建的安装程序。这让您的用户更轻松,因为他们不需要找到并安装适当的 java 运行 时间来使用您的应用程序。作为奖励,jlink 可以创建示例 shell 脚本来执行您的应用程序。通过 Maven 执行此操作很容易,使用 maven-javafx-plugin 的 jlink 选项并指定 jlinkZipName.

jpackage 可以创建一个 OS 级别的图标,用户只需点击即可进入 运行 应用程序,但不能直接从 maven-javafx-plugin.

其他资源