【问题标题】:JavaFX 11 : Create a jar file with GradleJavaFX 11:使用 Gradle 创建 jar 文件
【发布时间】:2019-03-05 07:13:20
【问题描述】:

我正在尝试将 JavaFX 项目从 8 Java 版本升级到 11 版本。它在我使用“运行”Gradle 任务(I followed the Openjfx tutorial)时有效,但是当我构建(使用“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'
        ]
    }
}

你知道我应该怎么做吗?

【问题讨论】:

    标签: gradle jar java-11 javafx-11


    【解决方案1】:

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

    如你所见here:

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

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

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

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

    当您将所有 jars 内容捆绑到一个 fat jar 中时,那些具有相同名称和相同位置的文件会发生什么情况?就算fat jar 把它们全都保存了,那怎么识别为一个单独的模块呢?

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

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

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

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

    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
    

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

    【讨论】:

    • 您是否检查了答案上发布的代码?它应该适合你,然后你应该以同样的方式更新你的代码。否则创建一个新问题。
    • 啊哈,我的错。在Main::main 方法中,我调用了HelloFX.launch(args),这是不允许的,而不是HelloFX.main(args)。调用HelloFX.launch(HelloFX.class, args) 很好,所以HelloFX::main 方法在这种情况下是多余的(例如,IDE 可以使用它)。
    • @JoséPereda,这仍然是答案吗?没有更新?
    • 我只尝试了您的编辑并得到了例外:Exception in thread "main" java.lang.NoClassDefFoundError: javafx/application/Application
    • @FearX 这个answer 已更新为使用 JavaFX gradle 插件。
    【解决方案2】:

    [编辑: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"
    

    【讨论】:

    • 我只定义了“build.gradle”文件。然后,我只是使用了 gradle 命令(如果你安装了 Gradle,你可以使用它)如果我没记错的话,由于 Gradle 中的 JavaFX 插件 (openjfx.io/openjfx-docs/#gradle),最后的 JavaFX 版本的配置更容易。所以现在所有这些步骤可能都没有用了。
    • 我们可以特别使用 jlink 来获取应用程序文件(“运行时图像”部分)
    • 我不明白。应用插件后,设置 build.gradle 我只需要运行 jLink 任务,我应该向我的客户分发什么? “构建”文件夹?我很困惑
    • 通过上面的方法(不是jlink),可以在build/libs中找到jar。使用 jlink,我想您可以在 build 的子文件夹中找到生成的可分发文件(一个包含 .bat 和 .sh 文件的 zip 文件来运行应用程序,如果我没记错的话)。
    • 是的。我不需要在我客户的电脑上安装 JRE?
    【解决方案3】:

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

    使用这些插件,您可以:

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

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

    【讨论】:

    • 太棒了。任何有兴趣的人,您也可以使用 Gradle installdist 任务,该任务将生成 /bin 和 /lib 目录,因此您不必像 assemble 那样解压缩 tar/zip。 jlink 命令也产生了一些输出,但在“build”下没有目录“image”。我对此一无所知,但第一个选项对我来说很好。
    • 我已尝试将它与 Java 11(我目前的操作系统版本)一起使用...这在 CLI 上运行良好,但 Eclipse 抱怨它再也找不到 FX 类了。知道为什么会这样吗?
    • 您是否将项目导入为 Gradle 项目?如果您将其作为普通项目打开,它可能无法正常工作。我使用 Intellij IDEA 创建了这个示例应用程序(因此它至少应该可以与 IDEA 一起使用)
    • 谢谢...我将其作为 Gradle 项目启动,是的。经过大量摆弄后,我已经缩小了问题范围:请参阅我刚刚提出的这个问题:stackoverflow.com/q/60552847/595305
    • 很棒的样板,谢谢。你应该让 jlink 的人把它添加到示例部分。
    猜你喜欢
    • 2020-01-21
    • 1970-01-01
    • 2021-04-24
    • 2021-05-21
    • 1970-01-01
    • 2019-05-01
    • 2019-06-15
    相关资源
    最近更新 更多