jdk17分别使用jlink打包javaFX应用和jpackage打包exe(包含第三方库处理)
本文唯一发布网站 博客园(官网地址:https://www.cnblogs.com/)
本文地址:https://www.cnblogs.com/zeromi/p/17227787.html
操作环境说明:
操作系统:windows11
(linux也可以参考本文操作)
jdk版本:openjdk-17+35
(理论上jdk9之后都可以按本文操作,具体是否可行,未验证)
javaFX版本:javafx-sdk-17.0.2
控制台:powershell
本文前提:项目代码需要已编译通过,笔者已经使用idea2022社区版编译通过,当然你也可以选择手动编译,使用IDE可以帮助我们更快完成这件事
powershell命令行太长时可以换行输入,windows下使用"`"换行,linux下使用"\"换行
1.对第三方jar进行模块信息注入
本项目导入的第三方库除javafx外,还有abmq-client-xxx.jar,而abmq-client-xxx.jar又引用了slf4j-api-xx.jar,javafx本身已经时模块化的,所以无需处理所以需要对剩下的这两个jar进行处理
建议按自底向上的方式进行处理,先处理slf4j-api-xx.jar,至于原因,下面会说明,这里我为了演示,先对abmq-client-xxx.jar注入模块信息
1.1.生成模块信息
jdeps是jdk自带的依赖分析工具
jdeps参数说明:
jdeps --ignore-missing-deps --generate-module-info . amqp-client-5.16.0.jar
执行完后,会发现当前文件夹出现了一个名为 com.rabbitmq.client的文件夹,里面就是生成的模块文件module-info.java
打印信息如下:
PS E:\codes\myidea\fxdemo> jdeps --ignore-missing-deps --generate-module-info . amqp-client-5.16.0.jar Warning: --ignore-missing-deps specified. Missing dependencies from com.rabbitmq.client are ignored writing to .\com.rabbitmq.client\module-info.java
PS E:\codes\myidea\fxdemo>
1.2.编译module-info.java
javac是jdk自带的编译器
需要将生成的module-info.java编译成class文件
javac参数说明:
javac -p .\slf4j-api-1.7.36.jar --patch-module com.rabbitmq.client=amqp-client-5.16.0.jar com.rabbitmq.client/module-info.java
结果发现编译报错,报错信息如下:
PS E:\codes\myidea\fxdemo> javac --patch-module com.rabbitmq.client=amqp-client-5.16.0.jar com.rabbitmq.client/module-info.java com.rabbitmq.client\module-info.java:12: 错误: 程序包为空或不存在: com.rabbitmq.tools exports com.rabbitmq.tools; ^ 1 个错误 PS E:\codes\myidea\fxdemo>
我苦思冥想,为啥“exports com.rabbitmq.client”没有报错,而“exports com.rabbitmq.tools”报错了,对比了一下差异,在amqp-client-5.16.0.jar文件的目录发现原因。
原来com.rabbitmq.tools文件夹下面没有class文件只有子文件夹,而com.rabbitmq.client文件夹下面有class文件,导致这个包无法导出。
知道原因,那么解决起来就简单了,用记事本打开module-info.java,删除exports com.rabbitmq.tools这行就行了。
重新编译顺利通过。
1.3.注入模块信息到jar文件
jar是jdk自带的一个打包工具
jar参数说明:
注意:这一步会修改这个jar文件,建议操作前备份jar文件
jar uf amqp-client-5.16.0.jar -C com.rabbitmq.client module-info.class
执行完后,使用压缩软件可以查看到jar文件内部根目录下已经多了一个module-info.class,到这里我们的对amqp-client-5.16.0.jar的模块信息注入就完成了。
接着slf4j-api-xxx.jar也按照上述流程执行一遍,完成模块信息注入。
1.4.运行java程序
java是java程序运行的基础命令
java参数说明:
执行下列命令:
java -p "E:\codes\myidea\fxdemo\lib;E:\codes\myidea\fxdemo\target\classes;D:\ProgramFiles\Java\javafx\javafx-sdk-17.0.2\lib" ` --add-modules=org.slf4j,com.rabbitmq.client,javafx.controls,javafx.fxml ` -m com.example.fxdemo/com.example.fxdemo.HelloApplication
很快啊,一个报错信息就拍我脸上了,内容如下:
PS E:\codes\myidea\fxdemo> java -p "E:\codes\myidea\fxdemo\lib;E:\codes\myidea\fxdemo\target\classes;D:\ProgramFiles\Java\javafx\javafx-sdk-17.0.2\lib" ` >> --add-modules=org.slf4j,com.rabbitmq.client,javafx.controls,javafx.fxml ` >> -m com.example.fxdemo/com.example.fxdemo.HelloApplication Exception in Application start method java.lang.reflect.InvocationTargetException at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:465) at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:364) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1071) Caused by: java.lang.RuntimeException: Exception in Application start method at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:901) at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:196) at java.base/java.lang.Thread.run(Thread.java:833) Caused by: java.lang.IllegalAccessError: class com.rabbitmq.client.ConnectionFactory (in module com.rabbitmq.client) cannot access class org.slf4j.LoggerFactory (in module org.slf4j) because module com.rabbitmq.client does not read module org.slf4j at com.rabbitmq.client/com.rabbitmq.client.ConnectionFactory.<clinit>(ConnectionFactory.java:55) at com.example.fxdemo/com.example.fxdemo.MsgSendController.<clinit>(MsgSendController.java:48) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480) at javafx.fxml/javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:939) at javafx.fxml/javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:981) at javafx.fxml/javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:230) at javafx.fxml/javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:755) at javafx.fxml/javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2808) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2634) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2548) at javafx.fxml/javafx.fxml.FXMLLoader.load(FXMLLoader.java:2516) at com.example.fxdemo/com.example.fxdemo.HelloApplication.start(HelloApplication.java:33) at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:847) at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:484) at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457) at java.base/java.security.AccessController.doPrivileged(AccessController.java:399) at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456) at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96) at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method) at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184) ... 1 more Exception running application com.example.fxdemo.HelloApplication PS E:\codes\myidea\fxdemo>
我一看
cannot access class org.slf4j.LoggerFactory
does not read module org.slf4j
这意思不就是找不到模块org.slf4j吗,我用记事本打开之前的生成的module-info.java一看,果然模块根本就没有引用org.slf4j
赶紧在里面加上一行(对于未模块化的jar,模块名一般就取包的根目录名,包根目录指从顶层开始一直到有class文件的目录)
(这也是我为啥建议按照依赖自底向上进行模块化注入,因为把依赖理清之后,才知道模块信息哪些地方需要修改)
requires org.slf4j;
最终的module-info.java内容如下:
module com.rabbitmq.client { requires java.naming; requires java.security.sasl; requires java.sql; requires org.slf4j; requires transitive java.desktop; exports com.rabbitmq.client; exports com.rabbitmq.client.impl; exports com.rabbitmq.client.impl.nio; exports com.rabbitmq.client.impl.recovery; exports com.rabbitmq.tools.json; exports com.rabbitmq.tools.jsonrpc; exports com.rabbitmq.utility; }
重新执行上述命令,对模块信息编译,这下应该稳了吧,然而:
PS E:\codes\myidea\fxdemo\lib> javac --patch-module com.rabbitmq.client=./amqp-client-5.16.0.jar com.rabbitmq.client/module-info.java
module-info.java:5: 错误: 找不到模块: org.slf4j
requires org.slf4j;
^
1 个错误
PS E:\codes\myidea\fxdemo\lib>
这个是因为amqp-client-5.16.0.jar引用了slf4j-api-xx.jar,所以我们只要在编译的时候带上slf4j-api-xx.jar的路径就行了,使用-p参数即可
个人猜想:
这里slf4j-api-1.7.36.jar还没有模块化,但是仍然可以编译通过,猜测可能是slf4j-api-xxx.jar被转成了自动模块(未验证)
这里如果两个jar都没有模块化是可以直接使用上述运行命令启动程序的(已验证)
javac -p ./slf4j-api-1.7.36.jar --patch-module com.rabbitmq.client=./amqp-client-5.16.0.jar ./com.rabbitmq.client/module-info.java
编译顺利通过,重复上述模块注入过程,分别对amqp-client-5.16.0.jar和slf4j-api-1.7.36.jar模块化后,再次运行java程序,ok启动成功,随意操作两下,没有出现任何问题,下一步开始打包。
2.jlink打包
2.1.jlink普通打包(大小约90M)
jlink --module-path "D:\ProgramFiles\Java\javafx\javafx-sdk-17.0.2\lib;E:\codes\myidea\fxdemo\lib;E:\codes\myidea\fxdemo\target\classes" ` --add-modules com.example.fxdemo ` --output app
jlink --module-path "D:\ProgramFiles\Java\javafx\javafx-jmods-17.0.2;E:\codes\myidea\fxdemo\lib;E:\codes\myidea\fxdemo\target\classes" ` --add-modules com.example.fxdemo ` --output app
cd app
./bin/java com.example.fxdemo.HelloApplication
2.2.jlink开启最高压缩选项打包(zip包,大小约45M,缩小了一半)
这个使用了--launcher选项,添加该选项后,jlink会根据你设定的名称在输出目录的bin文件夹下创建两个文件
本例使用的参数设置为Hello,那么生成的文件是Hello和Hello.bat
jlink --module-path "D:\ProgramFiles\Java\javafx\javafx-sdk-17.0.2\lib;E:\codes\myidea\fxdemo\lib;E:\codes\myidea\fxdemo\target\classes" ` --add-modules com.example.fxdemo --output appCompress ` --launcher Hello=com.example.fxdemo/com.example.fxdemo.HelloApplication --strip-debug --compress=2
jlink --module-path "D:\ProgramFiles\Java\javafx\javafx-jmods-17.0.2;E:\codes\myidea\fxdemo\lib;E:\codes\myidea\fxdemo\target\classes" ` --add-modules com.example.fxdemo --output appCompress ` --launcher Hello=com.example.fxdemo/com.example.fxdemo.HelloApplication --strip-debug --compress=2
运行应用
./bin/Hello
或者
./bin/java com.example.fxdemo.HelloApplication
4.jpackage打包成exe程序
要求:
必需:安装wix3.0版本(jdk17还不支持wix4.0),
非必需:对应平台的jmods,如果项目中使用到了javafx,就需要下载这个,否则打包会报错,具体错误可以查看本文目录错误记录:exe运行不成功
这里笔者系统为64位的win平台,所以下载的文件为openjfx-17.0.2_windows-x64_bin-jmods.zip,可以通过javaFX官网找到下载链接,找到后解压即可
打包成exe后,大小在为26.6M,生成的exe会带一个安装引导程序
具体请参考笔者另一篇文章:
使用jpackage将java程序打包成exe程序(不需要安装jdk即可运行)(https://www.cnblogs.com/zeromi/p/14852323.html)
jpackage --type exe -p "E:\codes\myidea\fxdemo\lib;E:\codes\myidea\fxdemo\target\classes;D:\ProgramFiles\Java\javafx\javafx-jmods-17.0.2" `
-n fxdemo -m com.example.fxdemo/com.example.fxdemo.HelloApplication `
--add-modules java.base,org.slf4j,com.rabbitmq.client,javafx.controls,javafx.fxml `
--vendor cy --verbose --win-console --win-dir-chooser --win-shortcut
实际上不加--add-modules也可以打包
jpackage --type exe -p "E:\codes\myidea\fxdemo\lib;E:\codes\myidea\fxdemo\target\classes;D:\ProgramFiles\Java\javafx\javafx-jmods-17.0.2" ` -n fxdemo -m com.example.fxdemo/com.example.fxdemo.HelloApplication ` --vendor cy --verbose --win-console --win-dir-chooser --win-shortcut
5.错误记录
找不到模块错误
PS E:\codes\myidea\fxdemo> java -p "E:\codes\myidea\fxdemo\lib;E:\codes\myidea\fxdemo\target\classes;D:\ProgramFiles\Java\javafx\javafx-sdk-17.0.2\lib" `
>> --add-modules=org.slf4j,com.rabbitmq.client,javafx.controls,javafx.fxml `
>> -m com.example.fxdemo/com.example.fxdemo.HelloApplication
Error occurred during initialization of boot layer
java.lang.module.FindException: Error reading module: E:\codes\myidea\fxdemo\lib\com.rabbitmq.client
Caused by: java.lang.module.InvalidModuleDescriptorException: Package com.rabbitmq.client.impl.nio not found in module
PS E:\codes\myidea\fxdemo>
原因是“E:\codes\myidea\fxdemo\out\artifacts\fxdemo\lib”目录中存在文件夹“com.rabbitmq.client”,导致java认为这个是正常的包需要解析,但是这个文件夹中只有module-info.java和module-info.class
注意清理掉编译文件夹中无关的文件,否则可能会引起异常报错,或编译的程序出现异常行为
找到我们的程序安装目录,笔者安装位置:D:\ProgramFiles\fxdemo
然后鼠标右键打开powershell,执行下列命令手动启动
.\fxdemo.exe
报错信息如下:
PS D:\ProgramFiles\fxdemo> .\fxdemo.exe Graphics Device initialization failed for : d3d, sw Error initializing QuantumRenderer: no suitable pipeline found java.lang.RuntimeException: java.lang.RuntimeException: Error initializing QuantumRenderer: no suitable pipeline found at javafx.graphics/com.sun.javafx.tk.quantum.QuantumRenderer.getInstance(Unknown Source) at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.init(Unknown Source) at javafx.graphics/com.sun.javafx.tk.Toolkit.getToolkit(Unknown Source) at javafx.graphics/com.sun.javafx.application.PlatformImpl.startup(Unknown Source) at javafx.graphics/com.sun.javafx.application.PlatformImpl.startup(Unknown Source) at javafx.graphics/com.sun.javafx.application.LauncherImpl.startToolkit(Unknown Source) at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(Unknown Source) at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(Unknown Source) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.base/java.lang.reflect.Method.invoke(Unknown Source) at java.base/sun.launcher.LauncherHelper$FXHelper.main(Unknown Source) Caused by: java.lang.RuntimeException: Error initializing QuantumRenderer: no suitable pipeline found at javafx.graphics/com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.init(Unknown Source) at javafx.graphics/com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(Unknown Source) at java.base/java.lang.Thread.run(Unknown Source) Exception in thread "main" java.lang.reflect.InvocationTargetException at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.base/java.lang.reflect.Method.invoke(Unknown Source) at java.base/sun.launcher.LauncherHelper$FXHelper.main(Unknown Source) Caused by: java.lang.RuntimeException: No toolkit found at javafx.graphics/com.sun.javafx.tk.Toolkit.getToolkit(Unknown Source) at javafx.graphics/com.sun.javafx.application.PlatformImpl.startup(Unknown Source) at javafx.graphics/com.sun.javafx.application.PlatformImpl.startup(Unknown Source) at javafx.graphics/com.sun.javafx.application.LauncherImpl.startToolkit(Unknown Source) at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(Unknown Source) at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(Unknown Source) ... 5 more Failed to launch JVM PS D:\ProgramFiles\fxdemo>
看到报错是no suitable pipeline found,No toolkit found,猜测是因为没有引用jmod打包(这里用的jar打包),换jmod文件打包就好了,打包命令:打包命令
附:
模块分析
可以直接使用idea等IDE查看依赖关系,尤其是在使用maven的情况下,建议使用IDE。
jdeps经测试仅支持jar,class文件依赖分析,不支持jmod
分析模块com.example.fxdemo依赖:
--module-path:依赖库路径
-m:指定解析根模块
jdeps --module-path "E:\codes\myidea\fxdemo\lib;D:\ProgramFiles\Java\javafx\javafx-sdk-17.0.2\lib;E:\codes\myidea\fxdemo\target\classes" -m com.example.fxdemo
分析模块com.rabbitmq.client依赖:
jdeps --module-path "E:\codes\myidea\fxdemo\lib" -m com.rabbitmq.client
参考和引用
参考和引用下列文章中的部分内容,未经过下列作者同意,如有存疑和其他问题请联系本人删除
java9 揭秘 jlink_使用jlink打包的java应用(https://blog.csdn.net/weixin_29781865/article/details/114759641)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报