如何为jlink打包的JavaFX自定义运行时镜像添加依赖未包含JDK类的可插拔子模块
嗨,刚好之前我也折腾过类似的场景,来给你梳理几个可行的解决方案,帮你搞定这个可插拔子模块的问题😉
首先得明确核心矛盾:你用jlink打包的基础镜像是经过裁剪的,只包含了你当初打包时指定的那些模块,子模块如果用到了基础镜像里没有的JDK模块,直接跑肯定会报类找不到的错误。针对这个问题,有几个不同方向的解决思路,你可以根据自己的需求选:
1. 提前预留可能用到的JDK模块(最简单但不够灵活)
如果你能提前预判子模块可能会用到哪些JDK模块,比如java.sql、java.naming这类,那在最初用jlink打包基础镜像的时候,就把这些模块一起加进去就行。举个例子,原来的打包命令可能是这样:
jlink --module-path javafx-jmods:jdk-jmods --add-modules javafx.controls,javafx.fxml --output base-image
那现在就把需要的JDK模块加在--add-modules后面:
jlink --module-path javafx-jmods:jdk-jmods --add-modules javafx.controls,javafx.fxml,java.sql,java.naming --output base-image
这种方式的优点是子模块直接就能用,不需要额外配置;缺点也很明显——如果子模块用到的模块太多,基础镜像会变大,失去jlink裁剪体积的意义,而且如果后续子模块用到了新的未预留模块,还是得重新打包基础镜像。
2. 运行时动态加载子模块和依赖的JDK模块(灵活,不影响基础镜像)
如果想保持基础镜像的小巧,同时支持可插拔的子模块,那可以在运行主应用的时候,通过参数指定额外的模块路径和要加载的模块。这里分两种情况:
子模块是模块化JAR(带module-info.java)
如果你的子模块是符合Java模块化规范的,那在它的module-info.java里要声明好依赖的JDK模块,比如:
module com.your.submod { requires javafx.controls; requires java.sql; // 子模块依赖的JDK模块 exports com.your.submod.api; // 暴露给主应用的API }
然后你需要从完整JDK的jmods目录里,把这些缺失的JDK模块(比如java.sql.jmod)复制到一个单独的目录,比如submod-deps。之后运行主应用的时候,加上这些参数:
# Linux/macOS ./base-image/bin/java --module-path submod-deps:your-submod.jar --add-modules com.your.submod -m com.your.main.module/com.your.main.MainClass # Windows base-image\bin\java --module-path submod-deps;your-submod.jar --add-modules com.your.submod -m com.your.main.module/com.your.main.MainClass
这样JVM会在运行时动态加载这些额外的模块,完全不会改动原来的基础镜像。
子模块是非模块化JAR(传统JAR)
如果子模块还是传统的非模块化JAR,那可以用--class-path指定子模块的路径,同时用--add-modules手动加上它依赖的JDK模块:
# Linux/macOS ./base-image/bin/java --add-modules java.sql --class-path your-submod.jar -m com.your.main.module/com.your.main.MainClass # Windows base-image\bin\java --add-modules java.sql --class-path your-submod.jar -m com.your.main.module/com.your.main.MainClass
这种方式虽然也能工作,但不如模块化JAR的依赖管理清晰,建议尽量把子模块改成模块化的。
3. 用jlink扩展基础镜像(生成包含子模块的新镜像)
如果想实现“安装子模块后,用户直接运行镜像就能用,不需要额外参数”,那可以基于原来的基础镜像,用jlink重新打包,把子女模块和它依赖的JDK模块加进去。比如:
jlink --module-path base-image/modules:your-submod.jar:/path/to/full-jdk/jmods --add-modules com.your.submod,java.sql --output extended-image
这里的base-image/modules是原来基础镜像里的模块目录,/path/to/full-jdk/jmods是完整JDK的模块目录,--add-modules指定要添加的子模块和缺失的JDK模块。运行完这个命令后,生成的extended-image就是包含了子模块的新镜像,用户直接运行这个镜像里的启动脚本就行。
然后回答你最后一个问题:能不能添加多个增量的lib/modules文件给JVM?
完全可以!JVM的--module-path支持指定多个路径,路径之间用冒号(Linux/macOS)或分号(Windows)分隔。你可以把不同子模块的依赖模块分别放到不同的目录,比如submod1-deps、submod2-deps,然后在运行时这样指定:
--module-path submod1-deps:submod2-deps:submod1.jar:submod2.jar
JVM会自动扫描这些路径里的所有模块,按需加载,非常灵活。
最后给你几个小建议:
- 尽量把子模块做成模块化JAR,这样依赖关系更清晰,JVM的模块加载机制能更好地帮你处理依赖问题
- 可以用
./base-image/bin/java --list-modules命令查看基础镜像里包含的所有模块,对比子模块需要的模块,快速找出缺失的部分 - 如果子模块需要用到的JDK模块比较多,又不想提前预留,那运行时动态加载的方式会更适合你
备注:内容来源于stack exchange,提问作者du fei

