将 JavaFX 应用程序与 openjdk 11 + 运行时捆绑在一起

Bundle JavaFX app with openjdk 11 + runtime

我创建了一个依赖 OpenJDK 11 和 JavaFX 的小型 HelloWorld Java 应用程序。该应用程序打包在一个 jar 文件中,如果我在我的系统上分别安装了 Java 11 和 JavaFX,则该文件只能是 运行。

现在我想将我的 jar 转换成 self-contained Java application,其中包括 JavaFX 和功能齐全的 Java 运行time 环境。这将允许我 运行 我的应用程序而无需安装 OpenJDK 11(它存在技术障碍,例如正确设置 PATH 等)。

我可以找到有关在 Java 10 上创建自包含 Java 应用程序的信息,但我找不到有关将 Java 应用程序与 OpenJDK 11 和 Java 捆绑在一起的信息外汇.

如何使用 OpenJDK 11 和 JavaFX 发布独立的 Java 应用程序(包括 java 运行 时间)?

也许您只需稍等片刻,直到新 jpackager 工具的第一个 EA 版本可用。参见 http://mail.openjdk.java.net/pipermail/core-libs-dev/2018-October/056186.html

您可以将整个 JDK 与您的应用捆绑在一起,并使用捆绑的 JDK 创建批处理脚本 运行 您的应用。我知道这种方法会使您的版本显着膨胀,但另一种方法是要求您的用户自己安装 JDK,这对于不懂技术的人来说并非易事。或者您可以发布两个版本,一个捆绑 JDK,一个不捆绑。

本地库

我遇到的一个挑战是告知 JavaFX 它自己的本地库(.dll.dylib.so 等)。幸运的是,加载动态库就像使用 System.setProperty(...).

设置 java.library.path 一样简单

从历史上看,设置此变量 argued/perceived 在 Java 中毫无意义,因为它对类加载器来说为时已晚(不如 -Djava.library.path)并且使用反射强制它是 forbidden security violation since Java 10... fortunately, JavaFX actually honors this variable naturally 没有任何违规行为或黑客攻击,设置后会把它捡起来。

// Detect the path to the currently running jar
String jarPath = new File(this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath()).getCanonicalPath();

// Fix characters that get URL encoded when calling getPath()
jarPath = URLDecoder.decode(jarPath, "UTF-8");

String parentFolder = new File(jarPath).getParent();

// If libglass.dylib is next to the jar in a folder called "/bin"
System.setProperty("java.library.path",  parentFolder + "/bin");

// ... then make any javafx calls

Java 图书馆

当然,.jar 文件也需要可访问。我这样做与 zipping them into the distribution 的任何 java 捆绑相同(制作一个大的 .jar 文件)

这些 .jar 文件应与所有 JavaFX 11 发行版一致,并应相应地捆绑。

javafx-swt.jar
javafx.base.jar
javafx.controls.jar
javafx.fxml.jar
javafx.graphics.jar
javafx.media.jar
javafx.swing.jar
javafx.web.jar

Java 8 兼容性

使用上述技术对 Java 8 进行的初步测试是肯定的。现在,我正在使用 Java 版本检测(不包括在上面的示例中)并且只为 Java 11 或更高版本设置 java.library.path。 Java 8 是个人使用 EOL 2019 年 12 月(商业用途 EOL 2019 年 1 月),因此在客户从一个 LTS 版本迁移到另一个 LTS 版本时提供兼容性很重要。

模块化Java、jlink、&jpackage

关注 these tutorials found at the new home for JavaFX after having been spun out of Oracle to Gluon.

您将需要使用模块化编写代码 Java。添加 JavaFX 11 作为项目的依赖项。并使用新的 linking/packaging 工具在您的应用程序中捆绑 JDK 的一个子集。

了解:

不再有 JRE

Oracle 不再打算让最终用户安装 JRE 或 JDK。 Java Applets in a browser and Java Web Start 应用程序交付都被逐步淘汰,最终用户不再需要 JRE。基于 Java 的应用程序应该捆绑自己的 Java 实现。唯一有意识地安装 JDK 的人将是开发人员和服务器端系统管理员。

重要:

这是一个流程图,可以帮助您在提供 Java 11 实施的各个供应商中找到并做出决定。


(使用 jdk14)

首先,为了使用 jlink 你的主 jar 应该是一个模块。

如何? 假设您有一个 maven 项目。您只需要在 src/main/java 目录中包含 module-info.java 并确保您 require 您的应用程序需要的模块和 export 包含您的主要内容的包 class.在大多数情况下,缺少 requires 时会出现编译时错误。请记住,非模块化依赖项变为 .

您可以使用 Maven 的 copy-dependencies 来确保在 mvn package.

期间将所有依赖项都复制到 target/lib

下一步:jlink

因为jlinkmaven插件还在alpha,你可以使用命令行。

备注:

  • jlink 将创建一个独立的包目录,其中包含
    • 主应用程序模块
    • 应用依赖项
    • jdk 所需模块
    • 应用程序启动器(可选)
  • jlink 捆绑包针对 一次一个平台 默认它是当前平台。
  • javafx 运行时间模块也是平台特定的。但由于它们不是jdk的一部分,我们需要始终提供包含它们的模块路径
  • javafx运行时间模块可以downloaded from web, or from maven repo使用相应的目标平台classifier(win/linux/mac).
  • jlink 还可以创建 跨平台包 。只需将目标平台模块包含到 --module-path(例如来自 linux:下载 windows jdk/ javafx 并将它们的 jmods 目录添加到 module-path).

jlink 命令

案例 1:build 和 target 平台 相同

注意:/path-to/javafx-mods 需要提供给您的 modulepath,除非您使用 Maven(复制依赖项)在 lib/ 下复制所需的 javafx deps。


jlink --launcher run=jdk14Example/com.example.javafx.app.Main \
--module-path ./lib:javafx-jdk14-example-1.0.0.jar:/path-to/javafx-mods \
--add-modules=jdk14Example --output app-bundle

案例 2:build 和 target 平台 不同

# Building from linux for windows
jlink --launcher run=jdk14Example/com.example.javafx.app.Main  \
--module-path ./lib:javafx-jdk14-example-1.0.0.jar:/path-to/jdk-win/jmods:/path-to/javafx-mods-win \
--add-modules=jdk14Example --output app-bundle

结论:

在上述两种情况下,您都会得到一个包含独立应用程序的目录,该应用程序可以 运行 在未安装 java/javafx 的工作站上。

# if jlink targeted linux
app-bundle/bin/run

# if jlink targeted windows
app-bundle/bin/run.bat

# if jlink targeted mac
app-bundle/bin/run