jpms不直接解决包名冲突,而是通过强制模块隔离、显式exports声明和编译期拦截同名包导出,从源头杜绝split package问题。

Java 模块化系统(JPMS)本身不“解决”包名冲突,而是通过强制隔离 + 显式导出 + 编译期拦截,从源头上让包名冲突无法发生——特别是那种“两个 Jar 同时提供 com.example.utils.StringUtils”的经典 split package 问题。
用 module-info.java 阻断同名包导出
模块系统在编译阶段就拒绝危险结构:
- 如果 moduleA 和 moduleB 都声明
exports com.example.utils;,且都被放入模块路径,javac 或 jlink 会直接报错:
error: package com.example.utils is declared in more than one module - 即使某个第三方 Jar 是自动模块(如 guava-31.1-jre.jar → 模块名
guava),它也只能隐式导出一次该包;若你自己的模块也试图exports com.google.common.base;,同样触发编译失败 - 这意味着:只要模块图能成功解析,运行时就不可能出现两个同名包被同时加载的情况
严格区分模块边界,避免跨模块包泄露
传统 classpath 下,类加载器“看到什么就用什么”;模块化后,访问必须显式授权:
- 未在
module-info.java中exports的包,哪怕类是 public,其他模块也完全不可见 - 即使两个模块都包含
com.example.service.UserService,只要它们没互相导出、也没被同一模块 requires,JVM 就当它们彼此不存在 - 你的业务模块
com.example.order可以安全依赖requires spring.core;,而无需担心 Spring 内部是否也用了com.example.*这样的包名——它根本导不出,你也根本看不到
迁移老项目时防冲突的关键操作
面对大量无 module-info 的旧 Jar,不能放任不管:
- 把所有第三方 Jar 放到
--module-path而非-cp,它们自动成为“自动模块”,获得唯一模块名(基于文件名) - 禁止多个自动模块导出相同包:比如
commons-lang3-3.12.0.jar和commons-lang-2.6.jar都含org.apache.commons.lang,启动时会报:
ResolutionException: Module X exports package Y to module Z and module W - 解决方案:只保留一个新版,用
jar --delete或重命名剔除旧版;或用--limit-modules排除冲突模块
团队协作中真正管用的落地习惯
技术机制再强,也得靠规范兜底:
- 在架构文档里明确写死包前缀规则,例如:“所有内部模块包名必须以
com.company.prod.开头,禁止使用util、common等泛化词作二级包” - 用 Checkstyle 配置
IllegalImport规则,禁止import com.alibaba.fastjson.*;这类通配导入——既防意外覆盖,也减少对同名类的依赖模糊性 - CI 流水线加入
jdeps --multi-release 25 --list-deps扫描,提前发现隐式跨模块引用或未声明的 requires
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











