jlink打包javaFX应用和引用第三方库处理()

操作环境说明:

操作系统: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参数说明:

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.javaPS 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\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打包

2.1.jlink普通打包(大小约90M)

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

运行应用

./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

注意清理掉编译文件夹中无关的文件,否则可能会引起异常报错,或编译的程序出现异常行为

参考和引用

参考和引用下列文章中的部分内容,未经过下列作者同意,如有存疑和其他问题请联系本人删除

java9 揭秘 jlink_使用jlink打包的java应用(https://blog.csdn.net/weixin_29781865/article/details/114759641)

————————

操作环境说明:

操作系统: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参数说明:

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.javaPS 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\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打包

2.1.jlink普通打包(大小约90M)

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

运行应用

./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

注意清理掉编译文件夹中无关的文件,否则可能会引起异常报错,或编译的程序出现异常行为

参考和引用

参考和引用下列文章中的部分内容,未经过下列作者同意,如有存疑和其他问题请联系本人删除

java9 揭秘 jlink_使用jlink打包的java应用(https://blog.csdn.net/weixin_29781865/article/details/114759641)