打包非模块化 JavaFX 应用程序

Posted

技术标签:

【中文标题】打包非模块化 JavaFX 应用程序【英文标题】:Package a non-modular JavaFX application 【发布时间】:2019-06-01 10:45:17 【问题描述】:

我有一个 Java 8 应用程序,它使用 JavaFX 并且主类在其中扩展 javafx.application.Application 。目前,我将它作为一个胖罐子交付,它在 Oracle Java 8 上运行良好。

现在我希望它能够在 OpenJDK 11 上运行。要添加 JavaFX,我已经将 org.openjfx 中的工件添加到类路径中,并将它们包含在 fat jar 中。如果我从命令行启动我的 jar,我会得到

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

我找到了解决这个问题的两种可能的方法:

    肮脏的一个:编写一个不扩展应用程序的特殊启动器并绕过模块检查。见http://mail.openjdk.java.net/pipermail/openjfx-dev/2018-June/021977.html 干净的:将--module-path 和--add-modules 添加到我的命令行。这个解决方案的问题是,我希望我的最终用户能够通过双击来启动应用程序。

虽然我可以选择 1. 作为解决方法,但我想知道当前 (OpenJDK 11) 构建/交付非模块化 JavaFX 应用程序的可执行胖 jar 的预期方式是什么。有人可以帮忙吗?

【问题讨论】:

我怀疑在选择发布依赖于 javafx jar 的非模块化应用程序时,有什么比选项 1 更好的选择。 【参考方案1】:

这些是打包/分发(非模块化)JavaFX 11 最终应用程序的几个选项。大部分在OpenJFX官方docs中都有解释。

我将使用this sample 作为参考。我也会使用 Gradle。使用 Maven(不同的插件)可以完成类似的操作,甚至没有构建工具(但不推荐这样做......)。如今,构建工具是必须的。

脂肪罐

这仍然是一个有效的选项,但不是首选选项,因为它打破了模块化设计并将所有内容捆绑在一起,并且除非您注意这一点,否则它不是跨平台的。

对于给定的示例,您有一个这样的 build.gradle 文件:

plugins 
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.5'


repositories 
    mavenCentral()


dependencies 


javafx 
    modules = [ 'javafx.controls' ]


mainClassName = 'hellofx.HelloFX'

jar 
    manifest 
        attributes 'Main-Class': 'hellofx.Launcher'
    
    from 
        configurations.compile.collect  it.isDirectory() ? it : zipTree(it) 
    

注意Launcher 类的使用。正如 OP 所述或 here 解释的那样,现在需要一个不从 Application 扩展的启动器类来创建一个胖 jar。

运行 ./gradlew jar 会生成一个胖 jar (~8 MB),其中包含 JavaFX 类和您的当前平台的本机库。

您可以像往常一样运行java -jar build/libs/hellofx.jar,但只能在同一平台上运行。

如 OpenJFX 文档或 here 中所述,您仍然可以创建跨平台 jar。

在这种情况下,我们可以包含三个图形 jar,因为它们具有平台相关的代码和库。 Base、controls 和 fxml 模块是平台无关的。

dependencies 
    compile "org.openjfx:javafx-graphics:11.0.1:win"
    compile "org.openjfx:javafx-graphics:11.0.1:linux"
    compile "org.openjfx:javafx-graphics:11.0.1:mac"

./gradlew jar 现在将生成一个胖 jar (19 MB),可以分发到这三个平台。

(注意媒体和网络也有平台相关的代码/本地库)。

所以这与过去在 Java 8 上一样有效。但正如我之前所说,它破坏了模块的工作方式,并且与当今库和应用程序的分布方式不一致。

别忘了这些 jars 的用户仍然需要安装 JRE。

jlink

那么,如何在您的项目中分发一个已经包含本机 JRE 和启动器的自定义图像?

你会说如果你有一个非模块化的项目,那是行不通的。真的。但在讨论 jpackage 之前,让我们在这里检查两个选项。

运行时插件

badass-runtime-plugin 是一个 Gradle 插件,可从非模块化项目创建运行时映像。

有了这个 build.gradle:

plugins 
    id 'org.openjfx.javafxplugin' version '0.0.5'
    id 'org.beryx.runtime' version '1.0.0'
    id "com.github.johnrengelman.shadow" version "4.0.3"


repositories 
    mavenCentral()


dependencies 


javafx 
    modules = [ 'javafx.controls' ]


mainClassName = 'hellofx.Launcher'

runtime 
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']

当您运行./gradlew runtime 时,它将创建一个运行时及其启动器,因此您可以运行:

cd build/image/hellofx/bin
./hellofx

注意它依赖于影子插件,并且它也需要一个 Launcher 类。

如果您运行./gradlew runtimeZip,您可以获得一个大约 32.5 MB 的自定义图像的 zip。

同样,您可以将此 zip 分发给具有相同平台的任何用户,但现在不需要安装 JRE。

请参阅 targetPlatform 了解为其他平台构建映像。

走向模块化

我们一直认为我们有非模块化项目,这是无法改变的......但如果我们真的改变它呢?

走向模块化 变化不大:你添加一个module-info.java 描述符,并在其上包含所需的模块,即使这些是非模块化 jar(基于自动名称) )。

基于相同的样本,我将添加一个描述符:

module hellofx 
    requires javafx.controls;

    exports hellofx;

现在我可以在命令行上使用jlink,或者使用插件。 badass-gradle-plugin 是一个 gradle 插件,来自与前面提到的同一作者,它允许创建自定义运行时。

使用此构建文件:

plugins 
    id 'org.openjfx.javafxplugin' version '0.0.5'
    id 'org.beryx.jlink' version '2.3.0'


repositories 
    mavenCentral()


dependencies 


javafx 
    modules = [ 'javafx.controls' ]


mainClassName = 'hellofx/hellofx.HelloFX'

你现在可以运行了:

./gradlew jlink
cd build/image/bin/hellofx
./hellofx

./gradlew jlinkZip 获取可在同一平台的机器上分发和运行的压缩版本 (31 MB),即使没有安装 JRE。

如您所见,不需要影子插件或 Launcher 类。您还可以针对其他平台,或包含非模块化依赖项,例如 question。

jpackage

最后,有一个新工具可以创建可执行安装程序,您可以使用它来分发您的应用程序。

到目前为止还没有 GA 版本(可能我们必须等待 Java 13),但现在有两个选项可以将它与 Java 11 或 12 一起使用:

对于 Java/JavaFX 11,您可以找到 here 在 Java 12 上的 JPackager 上的初始工作的反向端口。有一篇关于使用它的好文章 here,还有一个使用它的 gradle 项目 here。

对于 Java/JavaFX 12,已经有一个 build 0 version 的 jpackage 工具将在 Java 13 中可用。

这是该工具的一个非常初步的使用:

plugins 
    id 'org.openjfx.javafxplugin' version '0.0.5'


repositories 
    mavenCentral()


dependencies 


javafx 
    version = "12-ea+5"
    modules = [ 'javafx.controls' ]


mainClassName = 'hellofx/hellofx.HelloFX'

def java_home = '/Users/<user>/Downloads/jdk-12.jdk/Contents/Home'
def installer = 'build/installer'
def appName = 'HelloFXApp'

task copyDependencies(type: Copy) 
    dependsOn 'build'
    from configurations.runtime
    into "$buildDir/libs"


task jpackage(type: Exec) 
    dependsOn 'clean'
    dependsOn 'copyDependencies'

    commandLine "$java_home/bin/jpackage", 'create-installer', "dmg",
            '--output', "$installer", "--name", "$appName",
            '--verbose', '--echo-mode', '--module-path', 'build/libs',
            '--add-modules', "$moduleName", '--input', 'builds/libraries',
            '--class', "$mainClassName", '--module', "$mainClassName"

现在运行 ./gradlew jpackage 会生成一个 dmg (65 MB),我可以分发它以供安装:

结论

虽然您可以坚持使用经典的 fat jar,但在迁移到 Java 11 及更高版本时,一切都应该是模块化的。新的(即将推出的)可用工具和插件,包括 IDE 支持,在此过渡期间提供帮助。

我知道我在这里展示了最简单的用例,当尝试更复杂的实际案例时,会出现几个问题……但我们应该更好地解决这些问题,而不是继续使用过时的解决方案。

【讨论】:

感谢何塞,这是一个非常广泛的答案。我将调查这些选项。其中一些看起来与我对 Maven 和 moditect 插件的尝试相似,但我在使用自动模块和 jlink 时遇到了困难,所以也许你的建议更容易使用。 Hallo José,一个问题:将最终应用程序(即它们不应该作为库使用/导入)移动到模块化结构是否有意义或值得? @elect 这是个好问题。在这一点上,这取决于你。主要好处之一是使用jlink 进行分发,但即使是未来的jpackage 工具也可以处理非模块化项目。 @elect 这确实是一篇好文章。他提到,作为替代方案,您始终可以使用您的应用程序构建一个 jar,然后分发它,而无需运行时。但是现在不再有 JRE(用户需要安装 JDK)。 jlink 准确地补充说,特定于您的应用程序的 JRE。两个用例:Scene Builder 不是模块化的,但它与 jpackage 一起分发,ScenicView 是模块化的并与jlink 一起分发。无论如何,这不是通过 cmets 进行的对话。 @KenobiShan 你检查过这个***.com/questions/54892406/… 吗?

以上是关于打包非模块化 JavaFX 应用程序的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 maven 部署 JavaFX 项目,包括自定义和非模块化依赖项?

如何使用 JavaFX 11(模块化)创建独立应用程序? [复制]

JavaFX概览

使用 Gradle 构建 JavaFX 应用程序

JavaFx:如何配置使用jfx打包java程序

JavaFx:如何配置使用jfx打包java程序