首页 > 编程语言 >jlink打包javaFX应用和引用第三方库处理

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

时间:2023-03-18 22:35:16浏览次数:49  
标签:java javaFX javafx fxdemo jar client jlink com 打包

操作环境说明:

操作系统: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.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

结果发现编译报错,报错信息如下: 

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

相关文章

  • pyinstaller打包python程序
    pyinstaller打包python程序1.pyinstaller安装安装命令:#升级pip版本>>>pipinstall-Upip#安装pyinstaller>>>pipinstallpyinstaller2.pyinstaller使用1.选项......
  • Maven使用package打包报错
    问题​ 使用Mavenpackage打jar包的时候报错了,如下图显示[INFO]---maven-resources-plugin:3.2.0:resources(default-resources)@springbootdemo2---[INFO]Usi......
  • pyinstaller 打包报错Failed to determine matplotlib‘s data directory
    #fromPyInstaller.utils.hooksimportexec_statement##mpl_data_dir=exec_statement(#"importmatplotlib;print(matplotlib.get_data_path())")#assert......
  • 本地playwright打包docker封装(chrome)
    拉取官方镜像:dockerpullmcr.microsoft.com/playwright/python:v1.31.0-focal运行:dockerrun-it--name=python_playwright-v/Users/kaka/miniconda3/envs/playwrig......
  • python工程打包成可执行文件
    1、将python打包成exe的方式python上常见的打包方式目是通过pyinstaller来实现的。pipinstallpyinstaller或者用镜像下载:#清华源pipinstallpyinstaller-i......
  • maui BlazorWebView+本地html 打包Android app 实现支付宝H5支付
     <BlazorWebViewx:Name="blazorWebView"HostPage="wwwroot/index.html"UrlLoading="blazorWebView_UrlLoading"BlazorWebViewInitialized="blazorWebView_Blazor......
  • maven 导入jar包 打包丢失
    1、基础maven<build><plugins><!--cleanlifecycle,seehttps://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle-->......
  • Sitecore 打包备份与恢复
    打包备份首先进入Desktop面板,选择PackageDesigner,之后一路操作之后填入sourcename恢复安装包时跳出来的选项网上博客的有关解释https://www.partech......
  • Linux下文档的压缩与打包
    Linux下最常见的压缩文件通常都是.tar.gz格式的,除此之外还有.tar、.gz、.bz2、.zip下面介绍Linux下最常见的后缀名所对应的压缩工具:.gz:表示由gzip压缩工具压缩的文件。......
  • spring boot打包引入本地依赖
    jar包<!--不写默认jar包--><packaging>jar</packaging><!--添加依赖groupId/artifactId/version可随意填写--><!--${project.basedir}当前项目绝对路径--><......