打包非模块化 JavaFX 应用程序
Package a non-modular JavaFX application
我有一个 Java 8 应用程序,它使用 JavaFX 并且主要 class 扩展
javafx.application.Application。目前,我将它作为一个 fat jar 提供,它 运行 在 Oracle Java 8.
上没问题
现在我希望它能够在 OpenJDK 11 上 运行。要添加 JavaFX,我已经将 org.openjfx 中的工件添加到 class 路径并将它们包括在脂肪罐中。如果我从命令行启动我的 jar,我得到
Error: JavaFX runtime components are missing, and are required to run this
application
我找到了解决这个问题的两种可能方法:
- 肮脏的:写一个不扩展Application的特殊启动器并绕过模块检查。参见 http://mail.openjdk.java.net/pipermail/openjfx-dev/2018-June/021977.html
- 干净的:将 --module-path 和 --add-modules 添加到我的命令行。这个解决方案的问题是,我希望我的最终用户能够通过双击启动应用程序。
虽然我可以使用 1. 作为解决方法,但我想知道目前(OpenJDK 11)build/deliver 非模块化 JavaFX 应用程序的可执行 fat jars 的预期方法是什么。有人可以帮忙吗?
这些是 packaging/distributing(非模块化)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
class 的使用。正如 OP 所提到或解释的 ,现在需要一个不从 Application
扩展的启动器 class 来创建一个 fat jar。
运行 ./gradlew jar
生成包含 JavaFX classes 和 的本机库的胖 jar (~8 MB)您的 当前平台。
您可以 运行 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"
}
./gradlew jar
现在将生成一个可以分发到这三个平台的 fat jar (19 MB)。
(注意媒体和 Web 也有依赖于平台的 code/native 库)。
所以这和以前在 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
时,它会创建一个 运行time 及其启动器,因此您可以 运行:
cd build/image/hellofx/bin
./hellofx
请注意,它依赖于影子插件,并且还需要启动器 class。
如果您 运行 ./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 插件,与前面提到的作者相同,允许创建自定义 运行time.
使用此构建文件:
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。
如您所见,不需要影子插件或启动器class。您还可以针对其他平台,或包含非模块化依赖项,例如 。
jpackage
最后,有一个新工具可以创建可执行安装程序,您可以使用它来分发您的应用程序。
到目前为止还没有 GA 版本(可能我们必须等待 Java 13),但是现在有两个选项可以与 Java 11 或 12 一起使用:
使用 Java/JavaFX 11 有一个来自 Java 12 上 JPackager 初始工作的后向端口,您可以找到 here. There is a nice article about using it here, and a gradle project to use it 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}"
}
现在 运行ning ./gradlew jpackage
生成一个 dmg (65 MB),我可以分发安装:
结论
虽然您可以坚持使用 classic fat jars,但在转向 Java 11 及更高版本时,一切都应该是模块化的。新的(即将推出的)可用工具和插件,包括 IDE 支持,在这一过渡期间提供帮助。
我知道我在这里介绍的是最简单的用例,当尝试更复杂的实际案例时,会出现几个问题...但我们应该更好地解决这些问题,而不是继续使用过时的解决方案。
我有一个 Java 8 应用程序,它使用 JavaFX 并且主要 class 扩展 javafx.application.Application。目前,我将它作为一个 fat jar 提供,它 运行 在 Oracle Java 8.
上没问题现在我希望它能够在 OpenJDK 11 上 运行。要添加 JavaFX,我已经将 org.openjfx 中的工件添加到 class 路径并将它们包括在脂肪罐中。如果我从命令行启动我的 jar,我得到
Error: JavaFX runtime components are missing, and are required to run this
application
我找到了解决这个问题的两种可能方法:
- 肮脏的:写一个不扩展Application的特殊启动器并绕过模块检查。参见 http://mail.openjdk.java.net/pipermail/openjfx-dev/2018-June/021977.html
- 干净的:将 --module-path 和 --add-modules 添加到我的命令行。这个解决方案的问题是,我希望我的最终用户能够通过双击启动应用程序。
虽然我可以使用 1. 作为解决方法,但我想知道目前(OpenJDK 11)build/deliver 非模块化 JavaFX 应用程序的可执行 fat jars 的预期方法是什么。有人可以帮忙吗?
这些是 packaging/distributing(非模块化)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
class 的使用。正如 OP 所提到或解释的 Application
扩展的启动器 class 来创建一个 fat jar。
运行 ./gradlew jar
生成包含 JavaFX classes 和 的本机库的胖 jar (~8 MB)您的 当前平台。
您可以 运行 java -jar build/libs/hellofx.jar
照常,但仅限于同一平台。
如 OpenJFX 文档或
在这种情况下,我们可以包括三个图形 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
现在将生成一个可以分发到这三个平台的 fat jar (19 MB)。
(注意媒体和 Web 也有依赖于平台的 code/native 库)。
所以这和以前在 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
时,它会创建一个 运行time 及其启动器,因此您可以 运行:
cd build/image/hellofx/bin
./hellofx
请注意,它依赖于影子插件,并且还需要启动器 class。
如果您 运行 ./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 插件,与前面提到的作者相同,允许创建自定义 运行time.
使用此构建文件:
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。
如您所见,不需要影子插件或启动器class。您还可以针对其他平台,或包含非模块化依赖项,例如
jpackage
最后,有一个新工具可以创建可执行安装程序,您可以使用它来分发您的应用程序。
到目前为止还没有 GA 版本(可能我们必须等待 Java 13),但是现在有两个选项可以与 Java 11 或 12 一起使用:
使用 Java/JavaFX 11 有一个来自 Java 12 上 JPackager 初始工作的后向端口,您可以找到 here. There is a nice article about using it here, and a gradle project to use it 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}"
}
现在 运行ning ./gradlew jpackage
生成一个 dmg (65 MB),我可以分发安装:
结论
虽然您可以坚持使用 classic fat jars,但在转向 Java 11 及更高版本时,一切都应该是模块化的。新的(即将推出的)可用工具和插件,包括 IDE 支持,在这一过渡期间提供帮助。
我知道我在这里介绍的是最简单的用例,当尝试更复杂的实际案例时,会出现几个问题...但我们应该更好地解决这些问题,而不是继续使用过时的解决方案。