44

我正在尝试将 JavaFX 项目从 8 Java 版本升级到 11 版本。当我使用“运行”Gradle 任务(我遵循 Openjfx 教程)时它可以工作,但是当我构建(使用“jar”Gradle 任务)并执行(使用“java -jar”)一个 jar 文件时,消息“错误: 缺少 JavaFX 运行时组件,需要运行此应用程序”。

这是我的 build.gradle 文件:

group 'Project'
version '1.0'
apply plugin: 'java'
sourceCompatibility = 1.11

repositories {
    mavenCentral()
}

def currentOS = org.gradle.internal.os.OperatingSystem.current()
def platform
if (currentOS.isWindows()) {
    platform = 'win'
} else if (currentOS.isLinux()) {
    platform = 'linux'
} else if (currentOS.isMacOsX()) {
    platform = 'mac'
}
dependencies {
    compile "org.openjfx:javafx-base:11:${platform}"
    compile "org.openjfx:javafx-graphics:11:${platform}"
    compile "org.openjfx:javafx-controls:11:${platform}"
    compile "org.openjfx:javafx-fxml:11:${platform}"
}

task run(type: JavaExec) {
    classpath sourceSets.main.runtimeClasspath
    main = "project.Main"
}

jar {
    manifest {
        attributes 'Main-Class': 'project.Main'
    }
    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

compileJava {
    doFirst {
        options.compilerArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'javafx.controls,javafx.fxml'
        ]
    }
}

run {
    doFirst {
        jvmArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'javafx.controls,javafx.fxml'
        ]
    }
}

你知道我应该怎么做吗?

4

3 回答 3

47

使用 Java/JavaFX 11,shadow/fat jar 将无法工作。

你可以在这里阅读:

此错误来自sun.launcher.LauncherHelperjava.base 模块。这样做的原因是 Main 应用程序扩展Application并具有一个 main 方法。如果是这种情况,LauncherHelper将检查 javafx.graphics 模块是否作为命名模块存在:

Optional<Module> om = ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME);

如果该模块不存在,则中止启动。因此,在这种情况下,不允许将 JavaFX 库作为类路径中的 jar。

更重要的是,每个 JavaFX 11 jarmodule-info.class在根级别都有一个文件。

当您将所有 jars 内容捆绑到一个单独的 fat jar 中时,那些具有相同名称和相同位置的文件会发生什么?即使胖罐子保留了所有这些,它如何识别为单个模块?

有一个支持这一点的请求,但尚未解决:http: //openjdk.java.net/projects/jigsaw/spec/issues/#MultiModuleExecutableJARs

提供一种方法来创建包含多个模块的可执行模块化“uber-JAR”,保留模块标识和边界,以便整个应用程序可以作为单个工件交付。

shadow 插件将所有其他依赖项捆绑到一个 jar 中仍然有意义,但毕竟你必须运行类似的东西:

java --module-path <path-to>/javafx-sdk-11/lib \
   --add modules=javafx.controls -jar my-project-ALL-1.0-SNAPSHOT.jar

这意味着,毕竟,您必须安装 JavaFX SDK(每个平台)才能运行使用来自 maven Central 的 JavaFX 依赖项的 jar。

作为替代方案,您可以尝试使用jlink创建轻量级 JRE,但您的应用程序需要模块化。

您也可以使用 Javapackager 为每个平台生成安装程序。请参阅http://openjdk.java.net/jeps/343,它将为 Java 12 生成一个打包程序。

最后,还有一个 Javapackager 的实验版本,可与 Java 11/JavaFX 11 一起使用:http: //mail.openjdk.java.net/pipermail/openjfx-dev/2018-September/022500.html

编辑

由于 Java 启动器检查主类是否扩展javafx.application.Application,并且在这种情况下,它需要 JavaFX 运行时作为模块(而不是 jars),使其工作的可能解决方法是添加一个的Main 类作为主类您的项目,该类将调用您的 JavaFX 应用程序类。

如果您有一个javafx11带有 Application 类的包:

public class HelloFX extends Application {

    @Override
    public void start(Stage stage) {
        String javaVersion = System.getProperty("java.version");
        String javafxVersion = System.getProperty("javafx.version");   
        Label l = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
        Scene scene = new Scene(new StackPane(l), 400, 300);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

}

然后你必须将这个类添加到那个包中:

public class Main {

    public static void main(String[] args) {
        HelloFX.main(args);
    }
}

在您的构建文件中:

mainClassName='javafx11.Main'
jar {
    manifest {
        attributes 'Main-Class': 'javafx11.Main'
    }
    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

现在你可以运行:

./gradlew run

或者

./gradlew jar
java -jar build/libs/javafx11-1.0-SNAPSHOT.jar

来自 FAT jar 的 JavaFX

最终目标是将 JavaFX 模块作为模块路径上的命名模块,这看起来像是一个快速/丑陋的解决方法来测试您的应用程序。对于分发,我仍然建议上述解决方案。

于 2018-09-29T19:18:28.513 回答
7

使用最新版本的 JavaFX,您可以使用两个 Gradle 插件轻松分发您的项目(javafxplugin 和 jlink)。

使用这些插件,您可以:

  • 创建一个包含所有需要的 jar 文件的可分发 zip 文件:它需要执行 JRE(使用 bash 或批处理脚本)
  • 使用 Jlink 为给定操作系统创建本机应用程序:不需要 JRE 来执行它,因为 Jlink 在分发文件夹中包含一个“轻量级”JRE(仅包括所需的 java 模块和依赖项)

如果你想要一个例子,我在 bitbucket 上做了一个例子。

于 2019-10-24T12:38:07.123 回答
6

[编辑:有关最新版本的 JavaFX,请查看我的第二个答案]

如果有人感兴趣,我找到了一种为 JavaFX11 项目(使用 Java 9 模块)创建 jar 文件的方法。我仅在 Windows 上对其进行了测试(如果该应用程序也适用于 Linux,我认为我们必须在 Linux 上执行相同的步骤才能获得适用于 Linux 的 JavaFX jars)。

我有一个“Project.main”模块(由 IDEA 创建,当我创建 Gradle 项目时):

 src
 +-- main
 |   +-- java
     |   +-- main
         |   +-- Main.java (from the "main" package, extends Application)
     |   +-- module-info.java
 build.gradle
 settings.gradle
 ...

module-info.java 文件:

module Project.main {
    requires javafx.controls;
    exports main;
}

build.gradle 文件:

plugins {
    id 'java'
}

group 'Project'
version '1.0'
ext.moduleName = 'Project.main'
sourceCompatibility = 1.11

repositories {
    mavenCentral()
}

def currentOS = org.gradle.internal.os.OperatingSystem.current()
def platform
if (currentOS.isWindows()) {
    platform = 'win'
} else if (currentOS.isLinux()) {
    platform = 'linux'
} else if (currentOS.isMacOsX()) {
    platform = 'mac'
}
dependencies {
    compile "org.openjfx:javafx-base:11:${platform}"
    compile "org.openjfx:javafx-graphics:11:${platform}"
    compile "org.openjfx:javafx-controls:11:${platform}"
}

task run(type: JavaExec) {
    classpath sourceSets.main.runtimeClasspath
    main = "main.Main"
}

jar {
    inputs.property("moduleName", moduleName)
    manifest {
        attributes('Automatic-Module-Name': moduleName)
    }
}

compileJava {
    inputs.property("moduleName", moduleName)
    doFirst {
        options.compilerArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'javafx.controls'
        ]
        classpath = files()
    }
}

task createJar(type: Copy) {
    dependsOn 'jar'
    into "$buildDir/libs"
    from configurations.runtime
}

settings.gradle 文件:

rootProject.name = 'Project'

和 Gradle 命令:

#Run the main class
gradle run

#Create the jars files (including the JavaFX jars) in the "build/libs" folder
gradle createJar

#Run the jar file
cd build/libs
java --module-path "." --module "Project.main/main.Main"
于 2018-10-02T20:18:03.983 回答