基于Gradle使用jlink构建部署JavaFX应用的问题排查
在IDEA中创建带Gradle支持的JavaFX项目时,IDE自动生成了如下build.gradle配置脚本:
plugins { id 'java' id 'application' id 'org.openjfx.javafxplugin' version '0.0.10' id 'org.beryx.jlink' version '2.24.4' id 'org.javamodularity.moduleplugin' version '1.8.10' apply false } group 'com.prototype' version '1.0' repositories { mavenCentral() } ext { junitVersion = '5.8.2' } tasks.withType(JavaCompile) { options.encoding = 'UTF-8' sourceCompatibility = '17' targetCompatibility = '17' } application { mainModule = 'com.prototype.simulationcrystalgrowth' mainClass = 'com.prototype.simulationcrystalgrowth.SimulationApplication' } javafx { version = '17.0.1' modules = ['javafx.controls', 'javafx.fxml', 'javafx.web'] } dependencies { implementation('org.controlsfx:controlsfx:11.1.1') implementation('com.dlsc.formsfx:formsfx-core:11.4.2') { exclude(group: 'org.openjfx') } implementation('net.synedra:validatorfx:0.2.1') { exclude(group: 'org.openjfx') } implementation('org.kordamp.ikonli:ikonli-javafx:12.2.0') implementation('org.kordamp.bootstrapfx:bootstrapfx-core:0.4.0') implementation('eu.hansolo:tilesfx:17.0.11') { exclude(group: 'org.openjfx') } testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}") } test { useJUnitPlatform() } jlink { imageZip = project.file("${buildDir}/distributions/app-${javafx.platform.classifier}.zip") options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages'] launcher { name = 'app' } } jlinkZip { group = 'distribution' }
执行build任务完成后,build目录下生成distributions文件夹,其中zip压缩包的目录结构如下:
bin目录下存放.sh、.bat两类启动脚本lib目录存放项目所需的全部jar模块
本地环境JAVA_HOME指向Java 17时,执行bat脚本可正常启动程序。预期jlink作为应用构建打包工具,能够生成类似exe格式的独立应用启动程序。
执行build任务的过程中,build.gradle中配置的jlink相关任务并未被调用,任务执行列表如下:
手动执行这些jlink任务时均出现相同报错,报错信息如下:
对build.gradle中提及的distributions/app路径存在疑惑,认为构建后应当输出其他形式的产物,需要明确当前操作存在的错误,以及使用jlink构建最终应当得到的输出产物。
1. build任务未触发jlink任务的原因
Gradle核心build任务默认只依赖标准Java插件、Application插件定义的构建链路任务,Badass JLink插件(即org.beryx.jlink)引入的jlink、jlinkZip任务不属于build的默认依赖链,不会自动执行。如果需要build时自动触发jlink打包,可手动添加任务依赖:
build.dependsOn jlinkZip
2. jlink任务执行报错的原因与修复
报错核心原因是jlink原生不支持将无module-info.java的自动模块(非模块化jar)打包进自定义运行时镜像。当前项目引入的controlsfx、formsfx、validatorfx、ikonli-javafx、bootstrapfx-core、tilesfx依赖中存在非模块化jar,直接执行jlink会触发模块解析错误。
修复方案:在jlink配置块中添加mergedModule配置,由插件自动将非模块化依赖合并为可被jlink识别的显式模块:
jlink { // 原有配置保留 imageZip = project.file("${buildDir}/distributions/app-${javafx.platform.classifier}.zip") options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages'] launcher { name = 'app' } // 新增合并模块配置 mergedModule { additive = true requires 'org.controlsfx.controls' requires 'org.kordamp.ikonli.javafx' requires 'org.kordamp.bootstrapfx.core' requires 'eu.hansolo.tilesfx' requires 'net.synedra.validatorfx' requires 'com.dlsc.formsfx.core' } }
也可以直接将Badass JLink插件升级到最新稳定版,新版本内置了非模块化依赖的自动探测与合并逻辑,多数场景下不需要手动编写mergedModule规则。
3. jlink的标准输出产物
jlink本身不会生成Windows下的exe安装包,它的核心作用是生成裁剪后的、自带精简JRE的自定义运行时镜像,目标机器不需要预装Java环境即可运行程序:
- 执行
jlink任务后,产物输出到build/image目录,目录结构包含bin、lib等子目录:bin目录下包含和launcher配置同名的原生可执行文件(Windows下为app.exe、Linux下为无后缀二进制、macOS下为可执行程序),以及对应平台的启动脚本,这个exe就是直接双击可运行的程序入口,不需要依赖bat脚本lib目录存放裁剪后的JRE运行时、项目及所有依赖的模块化jar包
- 执行
jlinkZip任务后,会将上述完整镜像目录压缩为zip包,输出到配置的build/distributions路径下,这个zip包就是可直接分发的免安装运行包。
如果需要生成带安装向导、桌面快捷方式、注册表项的exe安装包,需要额外搭配jpackage工具,可直接在Badass JLink插件的jlink配置块中添加jpackage相关配置实现,不需要单独调用命令。
4. 当前build生成的zip包来源
目前执行build后在distributions目录看到的zip包,是Gradle自带Application插件的distZip任务生成的产物,不是jlink输出:这个包内的jar包是独立分散的,运行时依赖系统全局配置的JAVA_HOME,和jlink生成的自带JRE的独立镜像属于两类完全不同的构建产物。
内容的提问来源于stack exchange,提问作者The Prototype

