打包非模块化 JavaFX 应用程序胖罐金林克

2022-09-02 02:47:41

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

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

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

我发现了两种可能的方法来解决这个问题:

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

虽然我可以用1去。作为一种解决方法,我想知道目前(OpenJDK 11)构建/交付非模块化JavaFX应用程序的可执行胖罐的预期方法是什么。任何人都可以帮忙吗?


答案 1

这些是打包/分发(非模块化)JavaFX 11 最终应用程序的几个选项。其中大多数都在官方的OpenJFX文档中进行了解释。

我将使用此示例作为参考。我也将使用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) }
    }
}

请注意类的使用。正如OP所提到的或这里解释的那样,现在需要一个不延伸的启动器类来创建一个胖罐。LauncherApplication

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

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

如 OpenJFX 文档或此处所述,您仍然可以创建一个跨平台 jar。

在这种情况下,我们可以包括三个图形jar,因为它们具有与平台相关的代码和库。基本模块、控件模块和 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现在将生产一个胖罐(19 MB),可以分发到这三个平台。

(注意 媒体和Web也有依赖于平台的代码/本机库)。

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

不要忘记,这些jar的用户仍然必须安装JRE。

金林克

那么,在项目中分发自定义映像(其中已包含本机 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

请注意,它依赖于影子插件,并且还需要一个启动器类。

如果运行 ,则可以为此自定义映像获取大约 32.5 MB 的 zip。./gradlew runtimeZip

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

有关为其他平台构建映像的信息,请参阅目标平台。

走向模块化

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

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

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

module hellofx {
    requires javafx.controls;

    exports hellofx;
}

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

使用此构建文件:

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

或者对于压缩版本(31 MB),即使没有安装JRE,也可以在同一平台的计算机中分发和运行。./gradlew jlinkZip

如您所见,不需要影子插件或启动器类。您还可以面向其他平台,或包括非模块化依赖项,如本问题所示。

jpackage

最后,还有一个新工具可用于创建可用于分发应用程序的可执行安装程序。

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

在 Java/JavaFX 11 中,有一个来自 Java 12 上 JPackager 初始工作的回溯端口,你可以在这里找到。这里有一篇关于使用它的好文章,还有一个在这里使用它的gradle项目。

在Java / JavaFX 12中,已经有一个build 0版本的工具将在Java 13中提供。jpackage

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

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}"
}

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

installer

结论

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

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


答案 2

推荐