Java模块描述符module-info.java仅声明依赖模块名和可见性,不支持版本号;版本锁定需依赖Maven/Gradle等构建工具统一管理,并通过模块路径控制与--validate-modules验证防止运行时冲突。
模块描述符(module descriptor)本身不直接锁定依赖版本,也无法防止运行时的“版本漂移”。这是个常见误解——java 的 module-info.java 只负责**声明模块依赖关系和可访问性**,不参与依赖解析或版本控制。真正锁定依赖版本、避免运行时加载错误版本的机制,需结合构建工具与模块系统协同实现。
模块描述符只声明“需要哪个模块”,不指定“要哪个版本”
Java 的模块系统(JPMS)在 requires 语句中只写模块名(如 requires com.fasterxml.jackson.databind;),不支持写版本号(如 requires com.fasterxml.jackson.databind@2.15.2;)。JVM 在运行时按模块路径(--module-path)查找匹配名称的模块,但不会校验版本。若多个版本的同一模块被放入模块路径,行为未定义,很可能因类加载顺序导致意外版本被选中。
靠构建工具(Maven/Gradle)完成版本锁定
版本约束必须在构建阶段落实。模块化项目仍需依赖 Maven 的 <dependencyManagement> 或 Gradle 的 platform / enforcedPlatform 来统一管理传递依赖版本:
- Maven 中使用
<dependencyManagement>声明jackson-bom,确保所有jackson-*模块版本对齐 - Gradle 中用
enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.15.2")强制所有 Jackson 依赖收敛到 2.15.2 - 构建产物(如 JAR)应只包含一个版本的模块 JAR,并通过
jar --describe-module验证其Automatic-Module-Name或真实模块名是否唯一
运行时防止多版本混入:模块路径需严格受控
即使构建时版本锁定,运行时若手动拼接模块路径(如 --module-path lib/a.jar:lib/b.jar:lib/old-jackson.jar),仍可能引入冲突版本。正确做法是:
- 避免显式列出单个 JAR,改用
--module-path target/modules,其中modules/目录由构建工具预组装,确保无重复模块 - 使用
jlink构建自定义运行时镜像,仅包含明确声明的模块及其闭包,彻底排除外部干扰 - 启动时加
--validate-modules参数,让 JVM 检查模块图中是否存在同名模块冲突(会报错退出)
补充建议:用模块封装 + 导出控制降低风险
虽然不能锁版本,但可通过模块设计减少漂移影响:
- 在
module-info.java中只requires真正需要的模块,避免宽泛依赖(如不用requires java.desktop;如果只用java.awt.Image) - 限制导出(
exports)范围,避免将内部实现类暴露给其他模块,降低对特定版本 API 的隐式依赖 - 对关键第三方模块(如 SLF4J、Jackson),在自己的模块中封装一层适配器,隔离底层版本变化

















