2022-06-10   


以前用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,

  1. 直接 jdeps --print-module-deps --ignore-missing-deps --module-path F:\javafx-sdk-17.0.7\lib .\app.jar 得出模块,可能会有些模块jdeps识别不到。
  2. 然后 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识别不到嘛
  3. 新建一个build文件夹,把jar放进去然后打包jpackage --name myapp --input build --main-jar .\app.jar --runtime-image my-jdk17 --type app-image

Q.E.D.


我并不是什么都知道,我只是知道我所知道的。