JavaFX 11:使用Gradle创建jar文件

45

我正在尝试将一个JavaFX项目从8 Java版本升级到11版本。当我使用“run”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'
        ]
    }
}

你知道我应该做什么吗?

3个回答

51

使用Java/JavaFX 11,阴影/胖JAR将不起作用。

您可以在这里阅读到:

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

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

如果没有该模块,则启动将被中止。因此,在类路径上具有JavaFX库作为jar在这种情况下是不允许的。 此外,每个JavaFX 11 jar都有一个module-info.class文件,位于根目录。当您将所有jar内容捆绑到一个单独的fat jar中时,具有相同名称和位置的这些文件会发生什么?即使fat jar保留了它们所有,它如何标识为单个模块? 有一个请求支持此功能,但尚未解决:http://openjdk.java.net/projects/jigsaw/spec/issues/#MultiModuleExecutableJARs 提供一种方法来创建可执行的模块化“uber-JAR”,其中包含多个模块,保留模块的身份和边界,以便整个应用程序可以作为单个工件进行传送。 阴影插件仍然有意义,可以将所有其他依赖项捆绑成一个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中央仓库中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运行时作为模块(而不是作为JAR文件)。使其正常工作的一个可能的解决方法是添加一个新的Main类,它将成为您项目的主类,并调用您的JavaFX Application类。
如果您有一个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

JavaFX from FAT jar

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

你有检查答案中发布的代码吗?它应该适用于你,然后你应该以同样的方式更新你的代码。否则请创建一个新问题。 - José Pereda
啊哈,我错了。在 Main::main 方法中,我调用了 HelloFX.launch(args),这是不被允许的,而应该调用 HelloFX.main(args)。但是调用 HelloFX.launch(HelloFX.class, args) 就可以了,所以在这种情况下 HelloFX::main 方法就是多余的(例如,IDE可以使用它)。 - user1803551
@JoséPereda,那仍然是答案吗?没有更新了吗? - FearX
2
我尝试使用您的编辑,但出现了异常: Exception in thread "main" java.lang.NoClassDefFoundError: javafx/application/Application - FearX
@FearX 这个答案已经更新,使用了JavaFX gradle插件。 - José Pereda

8
使用最新版本的JavaFX,您可以使用两个Gradle插件轻松分发您的项目(javafxplugin和jlink)。
通过这些插件,您可以:
  • 创建一个可分发的zip文件,其中包含所有所需的jar文件:它需要JRE来执行(使用bash或批处理脚本)
  • 使用Jlink为给定的操作系统创建本机应用程序:不需要JRE来执行它,因为Jlink在分发文件夹中包含“轻型”JRE(仅包括所需的java模块和依赖项)
如果您需要示例,请查看我在bitbucket上创建的示例

太好了。有兴趣的人,你也可以使用Gradle的installdist任务,它将生成/bin和/lib目录,因此您无需像使用assemble一样解压tar/zip文件。jlink命令也会产生一些输出,但在“build”下没有“image”目录。我对这个情况一无所知,但第一个选项对我来说很好。 - mike rodent
我尝试在Java 11(我的当前操作系统版本)中使用它...这在CLI上运行良好,但Eclipse抱怨它无法再找到FX类了。你有任何想法为什么会这样吗? - mike rodent
你是否将项目作为Gradle项目导入?如果你将其作为普通项目打开,它可能无法正常工作。 我使用Intellij IDEA创建了这个示例应用程序(所以至少在IDEA中应该可以工作)。 - Flpe
1
很棒的样板文件,谢谢。你应该向jlink团队提出请求将其添加到示例部分。 - norbertas.gaulia
谢谢夸奖,很高兴看到它有帮助。我想在请求将其添加到示例部分之前,我应该稍微改进一下README文件和注释。但是感谢您的建议。 - Flpe
显示剩余2条评论

6

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

如果有人感兴趣,我找到了一种方法来为JavaFX11项目(使用Java 9模块)创建jar文件。我只在Windows上测试过(如果应用程序也适用于Linux,则认为我们必须在Linux上执行相同的步骤以获取Linux的JavaFX jar文件)。

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

 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文件后,我只需要运行jLink任务,那么我应该分发什么给我的客户?是"build"文件夹吗?我很困惑。 - FearX
是的,我不需要在客户端电脑上安装JRE吗? - FearX
如果您创建一个JAR文件(使用没有jlink的方法),则需要一个JRE。但是,使用jlink时,您不需要一个JRE(它包含一个“轻量级”JRE,仅包含您需要的Java核心模块)。 - Flpe
谢谢,Flpe。我正在使用Jlink,但在将其分发到Windows时遇到了一些问题。 - FearX
我使用Jlink制作了一个示例。如果您感兴趣,请查看我将添加到此问题的新答案。 - Flpe
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接