继续Java的jpackage模块打包Linux可执行程序(包含第三方非模块化包)
使用 jpackage 生成运行时映像,对于 Linux 可以直接生成可执行文件,并附带运行环境无须额外安装Java环境即可运行;所有的 jar 被打包为单个 modules 文件,位于生成的最终运行环境文件的 runtime\lib 中;一个简单的 JavaFx 应用打包后竟然还会超过100M(同一个应用在Windows 下打包后不足100M)。
第三方包的处理,参考 痛苦玩转Java的jpackage模块打包Windows可执行程序(包含第三方非模块化包) 这个部分 Linux 和 Windows 没有什么不同;这算是 Java 跨平台的好处把。
我们已 Debian 12 为例,这是目前主流的开源免费操作系统,从 CentOS 作死之后,基本都转向了它。
首先要准备编译环境和打包用的 Shell 脚本;我们是 Maven 工程,我们需要安装它(Maven),然后需要 JDK,由于 Debian 基本上都会有默认,而我们的 JavaFX 通常需要高版本的 JDK 所以我们要采用独立的 JDK,我们只需要下载 JDK 压缩包,解压即可,不要安装和设置环境变量; 我们通常使用 Eclipse Temurin 的 OpenJDK。
# 查看有无系统更新
sudo apt update# 执行系统更新
sudo apt upgrade# 安装 Maven 假设 Java 环境已有默认
sudo apt install maven# 试试 Maven 是否就绪
mvn -version# 下载 JDK 21 URL 仅参考
wget https://github.com/adoptium/temurin21-binaries/releases/download/jdk-(省略).tar.gz# 解压 JDK 到目录
tar -zxvf jdk-(省略).tar.gz# 安装 git 用于获取工程
sudo apt install git
避坑1:不要在 Windows 解压 JDK 的 Linux 压缩包
Linux 的 JDK 包其中的文件具有 Linux 的系统属性,例如可执行,文件链接等;Windows 解压后会丢失这些属性,此后将文件上传到 Linux 是无法运行的。
准备打包的 SH 脚本,同时将我们的 Java Maven 工程克隆到 Linux 中。脚本需要放置到项目目录中,与 pom/xml 同级。
#!/bin/sh# 输出个提示信息
echo R11 JAVA JDK 21# 设定临时环境变量以使用 JDK 21 打包
export JAVA_HOME=../jdk-21.0.7+6
export PATH=$JAVA_HOME/bin:$PATH# 执行工程编译生成 JAR
mvn -f pom.xml clean package# 处理第三方非模块化JAR
./compile-module-info.shecho Build executable Operator USE JPackage
# https://docs.oracle.com/en/java/javase/17/docs/specs/man/jpackage.html# 移除JPackage输出目录
rm -rf publish/r11# 构建运行镜像
jpackage \--name r11\--type app-image\--app-version 2.1.2\--vendor www.joyzl.com\--copyright www.joyzl.com\--description "JOYZL R11"\--icon r11.png\--dest publish\--verbose \--module-path publish/r11-jar\--module-path publish/r11-jar/lib\--module com.joyzl.r11/com.joyzl.r11.Application\--add-modules jdk.charsets\--add-modules jdk.localedata\--jlink-options --compress=2\--jlink-options --no-header-files\--jlink-options --no-man-pages\--jlink-options --bind-services\--jlink-options --include-locales=zh-cn\--java-options -Xms256m\--java-options -Xmx2048m\--java-options -Dfile.encoding=UTF-8\--java-options -Duser.timezone=GMT+08
compile-module-info.sh
#!/bin/shecho R11 JAVA JDK 21
export JAVA_HOME=../jdk-21.0.7+6
export PATH=$JAVA_HOME/bin:$PATHecho 为非模块化的依赖的包注入 module-info.java DIR=publish/r11-jar/lib# 把我们准备的 module-info.java 复制到 maven 打包后的 jar 一起
cp module/* $DIR -r# 以下处理 PDFBox 模块化JAR=$DIR/fontbox-3.0.5.jar
javac -p $DIR --patch-module org.apache.fontbox=$JAR $DIR/org.apache.fontbox/versions/21/module-info.java
jar -u -f $JAR -C $DIR/org.apache.fontbox/versions/21 module-info.classJAR=$DIR/pdfbox-io-3.0.5.jar
javac -p $DIR --patch-module org.apache.pdfbox.io=$JAR $DIR/org.apache.pdfbox.io/versions/21/module-info.java
jar -u -f $JAR -C $DIR/org.apache.pdfbox.io/versions/21 module-info.classJAR=$DIR/pdfbox-3.0.5.jar
javac -p $DIR --patch-module org.apache.pdfbox=$JAR $DIR/org.apache.pdfbox/versions/21/module-info.java
jar -u -f $JAR -C $DIR/org.apache.pdfbox/versions/21 module-info.class
避坑2:JPackage打包输出目录如果有文件会停止打包
JPackage打包指定的输出文件目录,如果有任何之前的文件,它不会自动覆盖,这会导致打包停止,因此建议每次打包前删除,我们在打包执行脚本中添加了命令,每次删除目标目录。
避坑3:不要直接使用JPackage在当前项目Maven输出目录中打包
JPackage输入目录和输出目录不要有嵌套,因此不要指定在同一个目录,这会导致JPackage无限循环查找文件,而造成 OOM异常(老生常谈了)。
避坑4:在Windows编辑的Shell脚本上传到Linux不能执行
如果你在Windows编辑Linux的脚本文件传输到 Linux 执行可能会收到以下奇怪的错误:
-bash: ./build.sh: 权限不够
这是因为文件build.sh没有设置可执行属性。
-bash: ./compile-module-info.sh: 无法执行:找不到需要的文件
sh文件明明就在哪,但是执行的时候确报错:无法执行:找不到需要的文件;这是因为 Windows 的换行符(CRLF)和 Linux(LF) 不一致,额外保存 UTF-8 文件时不要指定 BOM UTF-8 ,BOM 的前几个字节会导致脚本运行异常;使用以下脚本转换下文件,将换行符搞成 Linux 的,修改脚本文件属性为可执行。
# 命令的含义就是把文件中的 \r 移除
sed -i 's/\r//' compile-module-info.sh
此时代码也下载完毕了,一起准备就绪,执行命令把;首次打包 Maven 会下载必要的依赖,耐心等待把, Maven 默认会把下载的JAR存储在 /home/joyzl/.m2 目录中,其中 “joyzl" 是当前用户名。
./build.sh
打包完成后,输出目录中将生成 Linux 可执行的程序文件,bin/r11 是可执行文件,它没有扩展名;其它目录是运行所需文件,注意我们仅打包的可执行文件,没有打包安装包。
如果您打包的是控制台程序,那么可以在命令行直接运行了,我们打包的是桌面程序,我们登录到 Debian 12 桌面,打开桌面控制台执行 bin/r11 程序启动了,窗口打开了。然后我们哭了,窗口中的所有中文显示为方框,随便点击一下,程序功能看起来正常。
排错1:JavaFx在Linux中运行时中文无法正常显示
网上查了一下,有大概几种解决办法:1 Linux 缺中文字体(安装),2 JavaFX 程序中代码加载字体, 3 JavaFx 程序中代码设置字体,4 JavaFx 程序中样式表设置字体, 5 配置JAVA_HOME/jre/lib/fonts/fontconfig.properties(JavaFX打包后没有这个了),6 复制Windows的字体到Linux系统(吐血)。
这些方法都可以解决问题,但是没有找出根本原因,只能算凑合着用的解决办法;我们必须找出原因,搜罗大量国内外文章之后,我们发现了端倪,首先有一篇比较全面的文章 OpenJFX/Font+Setup 可以指导我们排查问题,过程就不描述了,结论就是:JavaFX 默认字体名称(CSS -fx-font-family:)为 "System Regular" 字体族 "System",通过Linux系统命令 “$ fc-match sans.regular” 可输出其映射的实际字体文件 NotoSansCJK-Regular.ttc: "Noto Sans CJK SC" "Regular",其中的SC表示简体中文(Simplified Chinese),如此说来,更本不是 Linux 系统缺简体中文字体(那个Linux系统没有中文桌面呢,那不是也正确显示中文了吗),其实是 JavaFX 自身的问题。
网上有一篇文章跟踪了JavaFx的字体加载,解释了这个问题的原因,很遗憾我们丢失了链接,根本原因就是 JavaFx 在 Windows 、MacOS 和 Linux 分别采用了不同的默认字体加载机制,而 Linux 中的加载机制,恰好没有将 “System” 默认为 支持中文的字体。
关于JavaFX字体问题解决办法,请参考 彻底解决JavaFx在Linux中文无法正常显示的问题-CSDN博客。
以上是我们在开发 r11 程序时,为了通过 jpackage 将 javaFx 桌面程序打包为最终镜像时遇到的问题;r11 是一款用于将软件源代码导出为软件著作权代码文档的工具,推荐去官网 r11.joyzl.cn 下载试试。