平滑过渡旧版Java项目到模块化架构的核心是分阶段解耦、渐进暴露、验证先行:第一步识别隔离高内聚子模块并添加module-info.java;第二步将第三方库作自动模块并置于模块路径;第三步精准exports/opens控制封装边界;第四步用jdeps和编译器插件验证依赖完整性。

平滑过渡旧版 Java 项目到模块化架构,核心不是一步重写,而是分阶段解耦、渐进暴露、验证先行。重点在于保留原有运行能力的同时,逐步引入 module-info.java 并厘清依赖边界,避免“模块化即重构”的误区。
第一步:识别并隔离可模块化的业务单元
不急于给整个项目加 module-info.java。先从高内聚、低耦合的子模块入手,例如:
- 将独立的工具包(如自定义的
utils、common)抽成单独 Maven 模块,并添加module-info.java,只exports明确对外提供的包; - 把数据访问层(如
repository或mapper)单独建模,requires java.sql和数据库驱动模块,exports接口但不导出实现类; - 跳过 Spring Boot 主启动模块初期模块化——它天然依赖大量反射和动态类加载,可暂作“自动模块”运行。
第二步:用自动模块过渡第三方依赖
绝大多数老项目依赖的第三方库(如 MyBatis、Log4j、Jackson)尚未提供 module-info.class。此时 JVM 会将其识别为“自动模块”,名称通常由 JAR 文件名推断(如 mybatis-3.5.12.jar → 模块名 mybatis)。你只需:
- 确保这些 JAR 放在 模块路径(--module-path) 而非类路径(-cp);
- 在自己的
module-info.java中直接requires mybatis;,无需额外配置; - 若遇到
IllegalAccessError(比如反射访问内部类),用--add-opens临时放开,例如:java --add-opens java.base/java.lang=ALL-UNNAMED -p mods -m myapp/com.example.Main
第三步:控制导出与开放,守住封装底线
模块化真正的价值在于“按需暴露”。不要为了编译通过而 exports 整个包或 opens 所有包:
立即学习“Java免费学习笔记(深入)”;
-
exports com.example.service;—— 仅让其他模块能调用该包下的 public 类和方法; -
opens com.example.model;—— 仅当框架(如 Hibernate、Spring)需运行时反射访问字段时才加,且尽量限定到具体包; - 敏感工具类、内部配置、测试辅助类,一律不
exports,也不放入模块路径,保留在类路径中更安全。
第四步:用构建工具辅助验证与收敛
Maven 或 Gradle 本身不强制模块语义,但可配合插件提前发现问题:
- 启用
maven-compiler-plugin的release和modulepath参数,让编译器检查模块依赖是否完整; - 使用
jdeps --module-path mods --recursive myapp.jar分析实际依赖图,发现隐式依赖或未声明的requires; - 对关键模块开启
--illegal-access=deny运行参数,迫使暴露所有非法反射调用点,再逐个用--add-opens精准修复。


















