操作环境说明:
操作系统:windows11
(linux也可以参考本文操作)
jdk版本:openjdk-17+35
(理论上jdk9之后都可以按本文操作,具体是否可行,未验证)
javaFX版本:javafx-sdk-17.0.2
本文前提:项目代码需要已编译通过,作者已经使用idea2022社区版编译通过,当然你也可以选择手动编译,使用IDE可以帮助我们更快完成这件事
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参数说明:
--ignore-missing-deps:忽略错误依赖,否则会解析出来很多错误 --generate-module-info: 生成模块信息文件module-info.java,后面是模块信c息文件输出位置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参数说明:
-p 模块引用的第三方库,--module-path的参数简写 --patch-module 当前要打补丁的模块名和jar文件,参数形式:模块名=jar路径,示例:com.rabbitmq.client=amqp-client-5.16.0.jar或com.rabbitmq.client=./amqp-client-5.16.0.jarjavac -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参数说明:
u:--update,更新现有 jar 档案 f:--file=FILE,档案文件名。省略时, 基于操作使用 stdin 或 stdout(就是会根据命令内容使用输入流或输出流) -C:DIR,更改为指定的目录并包含以下文件注意:这一步会修改这个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参数说明:
-p:等价于--module-path,所有第三方类文件或jar文件目录,win使用“;”间隔,linux使用“:”间隔 "E:\codes\myidea\fxdemo\lib" 文件夹中是引用的第三方库slf4j-api-xxx.jar,ampq-client-xxx.jar,这两个模块会自动转换成自动模块所以不会报错 已经通过手动注入module-info,所以不会报模块错误 这个看项目情况 "E:\codes\myidea\fxdemo\target\classes" 当前项目编译好的class文件的包根目录。这个必须有,否则会报错找不到当前项目模块 "D:\ProgramFiles\Java\javafx\javafx-sdk-17.0.2\lib" 包含javafx的jar文件,javafx从jdk9开始从jdk中剥离出来了,也属于第三方,因为本项目使用了javaFX,所以这个看项目情况 --add-modules:所有引用的第三方包的模块名,使用“,”分隔执行下列命令:
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\out\artifacts\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\out\artifacts\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被转成了自动模块,未验证
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打包
jlink打包项目com.example.fxdemo,项目主类com.example.fxdemo/com.example.fxdemo.HelloApplication 引用了第三方模块com.rabbitmq.client,org.slf4j jar名称分别为abmq-client-xxx.jar,slf4j-api-xx.jar 这两个jar已经被手动注入了模块信息 命令行太长时可以换行输入,windows下使用"`"换行,linux下使用"\"换行 下列命令中存在多个参数时,windows下使用";"分隔,linux下使用":"分隔 jlink参数说明: --module-path:引用的第三方包目录(目录中包含jar或jmod文件),存在多个参数时,windows下使用";"分隔,linux下使用":"分隔 --add-modules:添加当前项目的要打包的模块名,存在多个参数时,使用","分隔 --output:打包文件输出目录 --launcher:启动选项,启动命令=模块名/启动类(全限定名,即带包名的) --strip-debug 不打包调式信息 --compress=2 开启2级别压缩2.1.jlink普通打包(大小约90M)
jlink打包运行环境(jlink不支持自动模块,所以引用链条上必须都是模块化的,否则需要手动转成模块化包)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-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
运行应用
运行应用(下面这个命令经测试是执行的Hello.bat,Hello文件无法执行)./bin/Hello
或者
./bin/java com.example.fxdemo.HelloApplication
3.错误记录
找不到模块错误
PS E:\codes\myidea\fxdemo> java -p "E:\codes\myidea\fxdemo\out\artifacts\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
注意清理掉编译文件夹中无关的文件,否则可能会引起异常报错,或编译的程序出现异常行为
参考和引用
参考和引用下列文章中的部分内容,未经过下列作者同意,如有存疑和其他问题请联系本人删除
Project Jigsaw: Quick Start Guide (openjdk.org)(https://openjdk.org/projects/jigsaw/quick-start)java9 揭秘 jlink_使用jlink打包的java应用(https://blog.csdn.net/weixin_29781865/article/details/114759641)
标签:java,javaFX,javafx,fxdemo,jar,client,jlink,com,打包 From: https://www.cnblogs.com/zeromi/p/17227787.html