JavaFX 11:使用 Gradle 创建一个 jar 文件

JavaFX 11 : Create a jar file with Gradle

我正在尝试将 JavaFX 项目从 8 Java 版本升级到 11 版本。它在我使用 "run" Gradle 任务 (I followed the Openjfx tutorial) 时有效,但是当我构建(使用 "jar" Gradle 任务)并执行(使用 "java -jar") 一个 jar 文件,消息 "Error: JavaFX runtime components are missing, and are required to run this application" 出现。

这是我的 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'
        ]
    }
}

你知道我该怎么做吗?

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

如你所见here:

This error comes from sun.launcher.LauncherHelper in the java.base module. The reason for this is that the Main app extends Application and has a main method. If that is the case, the LauncherHelper will check for the javafx.graphics module to be present as a named module:

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

If that module is not present, the launch is aborted. Hence, having the JavaFX libraries as jars on the classpath is not allowed in this case.

此外,每个 JavaFX 11 jar 在根级别都有一个 module-info.class 文件。

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

有支持此功能的请求,但尚未解决:http://openjdk.java.net/projects/jigsaw/spec/issues/#MultiModuleExecutableJARs

Provide a means to create an executable modular “uber-JAR” that contains more than one module, preserving module identities and boundaries, so that an entire application can be shipped as a single artifact.

shadow 插件仍然可以将所有 other 依赖项捆绑到一个 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 启动器检查主 class 是否扩展 javafx.application.Application,在这种情况下,它需要 JavaFX 运行 可用的时间模块(不是 jars),一个可能的解决方法,让它工作,应该添加一个 new Main class ,它将成为你项目的主要 class, class 将调用您的 JavaFX 应用程序 class.

如果您有 javafx11 包含应用程序 class 的程序包:

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);
    }

}

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

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

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

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

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

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

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

模块-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"

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

使用这些插件,您可以:

  • 创建包含所有需要的 jar 文件的可分发 zip 文件:它需要执行 JRE(使用 bash 或批处理脚本)
  • 使用 Jlink 为给定 OS 创建本机应用程序:不需要 JRE 来执行它,因为 Jlink 包含 "light" JRE(仅包含所需的 java 模块和依赖项)在分发文件夹中

我做了an example on bitbucket,如果你想要一个例子。