打包非模块化JavaFX应用程序

tar*_*ion 11 java javafx maven openjfx javafx-11

我有一个Java 8应用程序,它使用JavaFX,主类扩展了 javafx.application.Application.目前,我将它作为胖罐提供,它在Oracle Java 8上运行良好.

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

Error: JavaFX runtime components are missing, and are required to run this
application
Run Code Online (Sandbox Code Playgroud)

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

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

虽然我可以使用1.作为一种解决方法,但我想知道目前(OpenJDK 11)构建/交付非模块化JavaFX应用程序的可执行胖罐的方法.有人可以帮忙吗?

Jos*_*eda 22

这些是用于打包/分发(非模块化)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) }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意使用Launcher类.如OP所述或此处解释的,Application现在需要一个不延伸的启动器类来创建一个胖罐.

运行./gradlew jar产生脂肪罐子(约8 MB),其中包括JavaFX类,和本机库当前的平台.

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

正如OpenJFX文档或此处所述,您仍然可以创建跨平台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"
}
Run Code Online (Sandbox Code Playgroud)

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

(注意媒体和Web也有平台相关的代码/本机库).

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

并且不要忘记这些罐子的用户仍然需要安装JRE.

JLINK

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

你会说,如果你有一个非模块化项目,那将无法工作.真正.但在讨论jpackage之前,我们先来看看两个选项.

运行时,插件

坏蛋运行时,插件是一个摇篮插件,从非模块化项目创建运行时图像.

使用此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']
}
Run Code Online (Sandbox Code Playgroud)

当你运行./gradlew runtime它时会创建一个带有启动器的运行时,所以你可以运行:

cd build/image/hellofx/bin
./hellofx
Run Code Online (Sandbox Code Playgroud)

注意它依赖于shadow插件,它也需要一个Launcher类.

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

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

请参阅targetPlatform为其他平台构建图像.

走向模块化

我们一直认为我们有非模块化项目,而且无法改变......但是如果我们改变它会怎样?

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

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

module hellofx {
    requires javafx.controls;

    exports hellofx;
}
Run Code Online (Sandbox Code Playgroud)

现在我可以jlink在命令行上使用,或者使用插件.该坏蛋- gradle这个-插件是一个插件的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'
Run Code Online (Sandbox Code Playgroud)

你现在可以跑了:

./gradlew jlink
cd build/image/bin/hellofx
./hellofx
Run Code Online (Sandbox Code Playgroud)

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

如您所见,不需要shadow插件或Launcher类.您也可以定位其他平台,或者包含非模块化依赖项,例如此问题.

JPackage上

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

到目前为止还没有GA版本(可能我们将不得不等待Java 13),但现在有两种方法可以在Java 11或12中使用它:

使用Java/JavaFX 11,您可以在此处找到Java 12上JPackager的初始工作的后端口.有一个关于使用它一个很好的文章在这里,和gradle这个项目中使用它在这里.

对于Java/JavaFX 12,已经有一个将在Java 13中提供的该工具的0版本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}"
}
Run Code Online (Sandbox Code Playgroud)

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

安装程序

结论

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

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