以前用idea打包过javafx程序,然后最近要写个小工具给别人使用,然后发现idea无法打包了,查找后发现jdk 9及以上需要用第三方打包工具(https://www.jetbrains.com/help/idea/packaging-javafx-applications.html#troubleshoot)
然后试了maven插件javafx:jlink也不行,因为项目有老的依赖,jlink没法解决自动模块(Automatic Module)的问题。
重新查找资料了解了下解决方法然后记录下步骤,毕竟现在中文互联网找东西真是越来越不好找了。
1. 打包
配置打包插件,分别生成一个不包含依赖的jar,和一个包含所有依赖的lib文件夹
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<!-- 此处为main程序主入口,类的全路径-->
<mainClass>com.example.fx_demo.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
target下会生成lib文件夹和jar,我这里叫fx_demo-1.0-SNAPSHOT.jar
这时候以及已经可以运行javafx程序了
java -cp "./lib/*;fx_demo-1.0-SNAPSHOT.jar" --module-path lib --add-modules javafx.controls,javafx.fxml com.example.fx_demo.Main
注:因为lib里包含了javafx依赖,所以module-path不用像官方文档一样指定为PATH_TO_FX的路径。(https://openjfx.cn/openjfx-docs/#install-javafx)不然按照官方文档指定成PATH_TO_FX会报错java.lang.LayerInstantiationException: Package jdk.internal.jimage.decompressor in both module jrt.fs and module java.base
2. 分析依赖用于自定义运行时
jdeps --multi-release 11 --print-module-deps --ignore-missing-deps -cp target/lib --module-path target/lib target/fx_demo-1.0-SNAPSHOT.jar
注:如果有多发行版 jar 文件,需要指定–multi-release
然后会打印出类似如下模块信息:hutool.all,java.base,java.desktop,javafx.controls,javafx.fxml,modbus4j,org.kordamp.bootstrapfx.core
复制下来,待会儿自定义运行时要用。
3. 自定义java运行时
jlink --module-path "D:\ScoopApps\apps\zulufx11-jdk\current\lib" --output my-jdk11 --add-modules java.base,java.desktop,javafx.controls,javafx.fxml
注:jlink 要用和module-path,也就是javafx运行时下的,如果你电脑上有多个jdk,一定要注意,不然会报Module javafx.controls not found
之类的错,之前就是因为这个耗费了半天时间。
这里只添加jdk下的模块,因为我项目里有几个依赖属于自动模块,jlink无法处理,也没啥好的解决办法,要么你像我一样只包含jdk下的模块,要么自己反编译重写成java module jar。
然后生成了属于我们自己的java运行时: my-jdk11 ;90.8 MB,比完整运行时小了将近一半
打包成exe
jpackage --name myapp --input target\lib --main-jar fx_demo-1.0-SNAPSHOT.jar --runtime-image my-jdk11 --type app-image
jpackage还有一些别的参数,可以看官方文档(https://docs.oracle.com/en/java/javase/14/docs/specs/man/jpackage.html)
然后打开myapp文件夹双击exe运行吧。
总结:
java9新引入的module-info还没咋深入了解。目前来看在自定义java运行时这里有点用,毕竟如果依赖都是具名模块的话,可以生成一个比较小的java运行时,也不用添加外部依赖包。其实图省事的话,也可以不搞那么多步,不精简运行时,直接最后一步jpackage打包exe,但是应用体积会非常大。
2023/5/21: 其实还是不要分开依赖打包了,直接把依赖打包一起,然后就是项目不要加module-info,
- 直接
jdeps --print-module-deps --ignore-missing-deps --module-path F:\javafx-sdk-17.0.7\lib .\app.jar
得出模块,可能会有些模块jdeps识别不到。- 然后
jlink --module-path "F:\ScoopApps\apps\zulufx17-jdk\current" --output my-jdk17 --add-modules java.base,java.logging,javafx.controls,javafx.fxml,javafx.swing,java.sql
裁剪运行时,这里我遇到了坑,jdk之前用的temurin,我module-path是temurin+openjdk用;
把两个路径拼起来;然后裁剪出来的jdk启动不了jar,后面用zulu这种包含javafx的jdk就没问题了,如果启动报错class not found,记得回到上步,把class对应的模块加上,因为有些模块jdeps识别不到嘛- 新建一个build文件夹,把jar放进去然后打包
jpackage --name myapp --input build --main-jar .\app.jar --runtime-image my-jdk17 --type app-image
Q.E.D.