本文详解如何在 Gradle 构建中避免将 LWJGL 打包进“胖 JAR”(uber-jar),确保各模块保持独立、符合 JPMS 规范,并解决因模块路径冲突导致的 module contains package exported by another module 错误。
本文详解如何在 gradle 构建中避免将 lwjgl 打包进“胖 jar”(uber-jar),确保各模块保持独立、符合 jpms 规范,并解决因模块路径冲突导致的 `module contains package exported by another module` 错误。
在使用 LWJGL 3.x 构建模块化 Java 应用(如 OpenGL 图形程序)时,一个常见却易被忽视的问题是:错误地将 LWJGL 的模块 JAR 合并进自己的应用 JAR 中。这直接违反了 Java 平台模块系统(JPMS)的核心原则——每个模块必须以独立的 JAR 文件存在,且其 module-info.class 必须位于 JAR 根路径下,不可被覆盖或丢弃。
你当前的 build.gradle 中这段 jar 配置正是问题根源:
jar {
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
manifest {
attributes 'Main-Class': 'ch.l1chorpe.fractalscl.Main'
}
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}该配置强制构建一个 uber-jar(即把所有依赖类、资源全部解压并合并到单个 JAR 中)。当 LWJGL 的 org.lwjgl、org.lwjgl.glfw 等模块的 module-info.class 被解压后与你的 ch.l1chorpe.fractalscl 模块混在一起时,JVM 在启动时会检测到同一包(如 org.lwjgl.opengl)被多个模块导出,从而抛出类似以下错误:
Error occurred during initialization of boot layer java.lang.module.FindException: Module ch.l1chorpe.fractalscl reads package org.lwjgl.opengl from both org.lwjgl.opengl and org.lwjgl
✅ 正确做法:保持模块边界清晰
JPMS 要求模块必须以独立、未解压的 JAR 形式存在于模块路径(--module-path)上。因此,你需要:
立即学习“Java免费学习笔记(深入)”;
- 完全移除 jar 块中的 from { ... } 合并逻辑;
- 让 Gradle 仅生成你自己的模块 JAR(不含任何依赖);
- 依赖项(LWJGL 等)应作为独立模块 JAR 保留在模块路径中——这正是 jlink 或运行时 --module-path 的职责。
✅ 推荐修改方案(简洁安全):
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
// ✅ 移除整个 jar 块,或仅保留最小化声明:
// (因为 application 插件已通过 mainClass 自动处理 MANIFEST)
// jar { } // ← 完全删除此 block 即可若需显式控制 MANIFEST(如兼容非模块化启动),可简化为:
jar {
manifest {
attributes 'Main-Class': 'ch.l1chorpe.fractalscl.Main'
// 不再执行 from {...} —— 禁止合并依赖!
}
}⚠️ 注意:application.mainClass 和 jar.manifest.attributes 二者功能重叠。既然你已配置 application { mainClass = '...' },直接删除整个 jar 块是最干净、最不易出错的选择。
? 补充关键配置(确保模块路径正确)
- Gradle 会自动识别 module-info.java 并启用模块路径(modularity.inferModulePath = true 默认开启,Gradle 7.0+);
- jlink 插件已正确配置,它会扫描 runtimeClasspath 中所有模块 JAR(包括 LWJGL 的 lwjgl-*.jar),并将它们按 JPMS 规则组装进运行镜像;
- 确保 module-info.java 中的 requires 语句与实际依赖一致(你当前的 requires org.lwjgl; requires org.lwjgl.glfw; ... 是正确的);
- 运行时(如 gradle run)将自动使用模块路径而非类路径——前提是未手动干预 --class-path。
? 验证是否成功?
构建后检查 build/libs/ 目录:
- ✅ 应仅存在 fractalscl-1.0.0.jar(大小仅数百 KB,只含你自己的代码 + module-info.class);
- ❌ 不应包含 LWJGL 类或 org/lwjgl/ 包结构;
- ✅ build/distributions/FractalsCL-1.0.0.zip 解压后,lib/ 目录下应有 lwjgl-3.3.1.jar、lwjgl-glfw-3.3.1.jar 等独立模块 JAR。
此时,jlink 生成的镜像或 java --module-path ... --module ch.l1chorpe.fractalscl/ch.l1chorpe.fractalscl.Main 均能正常启动,无模块包冲突。
? 总结
| 误区 | 正确实践 |
|---|---|
| 把 LWJGL “打进” 自己的 JAR(破坏模块边界) | 让 LWJGL 保持独立模块 JAR,通过 --module-path 加载 |
| 用 duplicatesStrategy.EXCLUDE 掩盖模块冲突 | 删除 uber-jar 逻辑,尊重 JPMS 的模块隔离契约 |
| 依赖 jar.from{...} 实现“一键运行” | 使用 jlink 构建自包含镜像,或通过 java -p ... -m ... 显式指定模块路径 |
模块化不是“打包得更小”,而是“封装得更准”。移除冗余的 jar 合并逻辑,回归 JPMS 设计本意——每个模块各司其职、边界清晰,才是 LWJGL 3.x 在 Java 19+ 环境中稳定运行的基石。

















