迁移java 9模块化关键在于分阶段建立封装边界、显式声明依赖与可见性,并验证运行时行为;核心矛盾是类路径的宽松访问被强制转为“必须导出才可见、必须声明才可依赖”,成败取决于对这一约束转变的理解和落地节奏。

将传统非模块化项目迁移到 Java 9 模块化系统,关键不是一步重写,而是分阶段建立封装边界、显式声明依赖与可见性,并验证运行时行为是否符合预期。核心矛盾在于:类路径下“能加载就能用”的宽松访问,被模块系统强制转为“必须导出才可见、必须声明才可依赖”。迁移成败取决于对这一约束转变的理解和落地节奏。
第一步:准备环境并确保类路径兼容
在动 module-info.java 之前,先让项目在 JDK 9+ 上以纯类路径方式干净运行:
- 升级 JDK 至 9 或更高版本(推荐 17+ LTS),确认编译和运行无弃用警告(如
javax.xml.bind已移除) - 统一第三方库版本,避免同一类出现在多个 JAR 中引发
LinkageError - 禁用反射绕过封装的隐式调用(例如 Spring 的
setAccessible(true)若未配opens,后续必报IllegalAccessException) - 用
jdeps --jdk-internals扫描内部 API 使用,提前替换为标准替代方案
第二步:添加 module-info.java 并最小化导出
每个需模块化的子项目(Maven module / Gradle project)根目录 src/main/java 下新建 module-info.java:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 模块名建议与 Maven artifactId 一致(如
com.example.service),避免点号开头或纯数字 - 只
exports真正对外提供服务的 API 包(如exports com.example.service.api;) - 绝不导出
impl、internal、config等实现包;若框架需反射访问(如 Jackson 序列化内部类),改用opens com.example.service.internal;或限定到具体模块:opens com.example.service.internal to com.fasterxml.jackson.databind; - 依赖声明用
requires,第三方 JAR 若无模块描述,则成为“自动模块”,名称由 JAR 文件名推导(如guava-32.1.3-jre.jar→ 模块名guava),直接写requires guava;
第三步:处理依赖传递与跨模块变量访问
模块化后,“public static final”常量、跨包 new 实例、直接引用 impl 类等旧习惯会失效:
- 若某模块 A 的公开方法返回类型来自依赖模块 B(如
ResponseEntity<user></user>中User在com.example.model),则 A 必须requires transitive com.example.model;,否则使用者模块 C 编译失败 - 禁止模块外代码直接 new 实现类;应通过 SPI、工厂接口或依赖注入获取实例
- 原类路径下通过包名访问的 public 静态变量,需封装为接口方法(如
ConfigService.getTimeout()),并在 API 包中导出该接口 - 测试代码通常运行在“未命名模块”中,不受目标模块
exports限制——这是调试期的便利,但生产部署必须按正式模块规则校验
第四步:构建与运行时路径切换验证
混淆 --class-path 和 --module-path 是最常见故障源:
- Maven 用户:启用
maven-compiler-plugin的<release>17</release>,并配置modularity属性 - Gradle 用户:启用
java-library插件,设置java.toolchain.languageVersion = JavaLanguageVersion.of(17) - 运行命令必须用
--module-path(或简写-p),而非-cp;主类需指定完整模块名:java -p mods:lib -m myapp/com.example.Main - 用
jdeps --module-path mods/ --list-deps myapp.jar查看真实依赖图,确认没有意外的跨模块字段引用 - 故意移除某个
exports后,调用方应立即编译失败——这是解耦生效的标志,而非静默降级
不复杂但容易忽略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










