打包非模块化JavaFX应用程序

时间:2019-01-06 15:30:43

标签: java maven javafx javafx-11 openjfx

我有一个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. 肮脏的一个:编写一个特殊的启动器,该启动器不扩展Application并规避模块检查。参见http://mail.openjdk.java.net/pipermail/openjfx-dev/2018-June/021977.html
  2. 干净的方法:在我的命令行中添加--module-path和--add-modules。此解决方案的问题是,我希望我的最终用户能够通过双击来启动该应用程序。

虽然我可以将1.作为一种解决方法,但我想知道(OpenJDK 11)当前用于构建/交付非模块化JavaFX应用程序的可执行文件的方法是什么。有人可以帮忙吗?

1 个答案:

答案 0 :(得分:11)

这些是打包/分发(非模块化)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扩展的启动程序类来创建胖子。

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

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

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

在这种情况下,我们可以包括三个图形罐,因为它们是具有平台相关代码和库的图形罐。 Base,控件和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),可以将其分发到这三个平台。

(Note Media和Web也具有平台相关的代码/本机库)。

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

不要忘记,这些jar的用户仍将必须安装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

Going Modular

我们一直认为我们有非模块化项目,并且无法更改...但是如果我们更改它该怎么办?

采用模块化没什么大变化:您添加了一个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,表示即使没有安装JRE,也可以在同一平台的计算机中分发并运行的压缩版本(31 MB)。

如您所见,不需要影子插件或Launcher类。您也可以定位到其他平台,或包括非模块化依赖项,例如本question

jpackage

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

到目前为止,还没有GA版本(可能我们必须等待Java 13),但是现在有两种选择可以在Java 11或12中使用它:

对于Java / JavaFX 11,在Java 12上的JPackager上的最初工作有一个后端口,您可以找到here。有一篇关于如何使用它的不错的文章here,还有一个关于如何使用它here的gradle项目。

在Java / JavaFX 12中,jpackage工具中已有build 0 version随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),我可以分发该dmg以进行安装:

installer

结论

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

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