
本文详解在使用 gradle 构建模块化 java 应用时,为何不能将 lwjgl 等第三方模块打包进“fat jar”,以及如何通过移除 uber-jar 配置、启用标准模块路径机制,实现 lwjgl 的正确分离与模块化加载。
本文详解在使用 gradle 构建模块化 java 应用时,为何不能将 lwjgl 等第三方模块打包进“fat jar”,以及如何通过移除 uber-jar 配置、启用标准模块路径机制,实现 lwjgl 的正确分离与模块化加载。
在基于 Java 9+ 模块系统(JPMS)开发图形或高性能应用(如使用 LWJGL 3.x)时,一个常见却极易被忽视的错误是:将 LWJGL 的模块类强行合并进自己的应用 JAR 中。正如问题中所示,当前 build.gradle 中的 jar 块通过 from { configurations.runtimeClasspath.collect { ... } } 将所有依赖(包括 org.lwjgl.* 的 .jar 文件)解压并合并到单个 *.jar 中——这本质上构建了一个“uber-jar”(也称 fat jar)。而 JPMS 的核心设计原则之一正是模块隔离:每个模块必须以独立的 JAR 形式存在,并在其根路径下提供唯一的 module-info.class。当多个模块的 module-info.class 被强制合并(且 duplicatesStrategy(EXCLUDE) 仅随机保留其一),JVM 在模块解析阶段便会报错,典型提示如:
Module 'ch.l1chorpe.fractalscl' reads package 'org.lwjgl.glfw' from both org.lwjgl.glfw and org.lwjgl
这是因为你的应用模块 ch.l1chorpe.fractalscl 本应通过 requires org.lwjgl.glfw 声明对独立模块 org.lwjgl.glfw 的依赖,而非“拥有”它的字节码。
✅ 正确做法:回归模块化构建范式
Gradle 8.0+ 对 JPMS 具备原生支持。你无需手动打包依赖,而应让 Gradle 将模块依赖自动置于 --module-path,由 JVM 在运行时按需解析。关键修改如下:
1. 彻底移除 uber-jar 的 jar 块
// ❌ 错误:破坏模块边界的 uber-jar 构建
jar {
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
manifest { attributes 'Main-Class': 'ch.l1chorpe.fractalscl.Main' }
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}
✅ 替换为(或直接删除该 block):
// ✅ 正确:仅声明主类(可选,因 application{} 已覆盖)
jar {
manifest {
attributes 'Main-Class': 'ch.l1chorpe.fractalscl.Main'
}
}
⚠️ 注意:由于 application 插件已通过 mainClass = 'ch.l1chorpe.fractalscl.Main' 明确指定入口,此 jar 块实际已非必需,推荐直接删除整段 jar{...} 配置,避免歧义。
2. 确保模块路径正确启用
Gradle 会自动检测 src/main/java/module-info.java 并启用模块路径(--module-path)。请确认:
- module-info.java 位于 src/main/java/ 根目录(而非子包内);
- java { modularity.inferModulePath = true } 在 Gradle 7.0+ 中默认开启,无需显式配置(但可保留以增强可读性)。
3. 运行与分发:使用 jlink 或标准模块启动
- 开发期运行:执行 ./gradlew run 即可,Gradle 自动构造正确的 --module-path 和 --add-modules 参数。
- 生产分发:你已配置 org.beryx.jlink 插件,它会基于 module-info.java 的 requires 关系,从依赖中精准提取所需模块(含 LWJGL 及其 natives),生成轻量级自包含运行镜像。生成的 ZIP 中,你的应用 JAR 与 LWJGL 各模块 JAR 将严格分离存放,完全符合 JPMS 规范。
? 验证是否成功?
- 检查 build/libs/ 目录:应仅存在 fractalscl-1.0.0.jar(不含任何 LWJGL 类);
- 检查 build/distributions/ 下的 ZIP:解压后可见 fractalscl-1.0.0.jar 与 lwjgl-3.3.1.jar、lwjgl-glfw-3.3.1.jar 等并列存在;
- 运行 java --list-modules | findstr lwjgl(Windows)可确认 LWJGL 模块是否被 JVM 正确识别。
? 总结
- 核心原则:JPMS ≠ Class-Path;模块必须物理分离,不可合并。
- LWJGL 特别注意:其 natives(如 natives-windows)需通过 runtimeOnly 声明,jlink 插件会自动将其与对应模块绑定,无需手动处理 DLL/SO。
- Gradle 最佳实践:信任插件对模块路径的自动化管理,避免手动 zipTree 或 from 依赖 —— 这是传统 classpath 思维在模块化时代的典型陷阱。
遵循以上步骤,你的应用将真正以模块化方式运行,既获得 JPMS 的强封装性与启动性能优势,又确保 LWJGL 功能完整可用。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











