Java 9模块化迁移是分阶段收口依赖与封装边界的过程,需先确保类路径无警告运行,再从小模块开始添加module-info.java并严格控制exports/opens,最后用编译报错、jdeps和IllegalAccessError验证封装生效。

Java 9 模块化迁移不是重写,而是分阶段收口依赖与封装边界。关键不在于一步到位,而在于让编译器和 JVM 帮你“说话”——该报错时立刻报错,该拒绝访问时绝不妥协。
先确保类路径能干净运行
在动 module-info.java 之前,必须让项目在 JDK 9+ 上以纯 类路径(-cp)方式无警告启动。这步常被跳过,但它是迁移的地基:
- 替换已移除的 API:如
javax.xml.bind、java.activation、sun.misc.BASE64Encoder,改用java.util.Base64或添加对应模块(如--add-modules java.xml.bind) - 统一第三方库版本,避免同名类从多个 JAR 加载(jdeps 可查:
jdeps -summary -cp lib/*.jar out/) - 禁用隐式反射:Spring 等框架若用了
setAccessible(true)访问非 public 成员,需提前确认是否在模块化后仍被允许;否则后续会直接抛IllegalAccessError
从小模块开始加 module-info.java
选一个业务清晰、不被大量反射调用的子模块(比如配置中心或规则引擎),为其添加 module-info.java,并严格控制导出范围:
-
只 exports 接口包(如
com.example.config.api),绝不导出impl、internal或model实现细节 - 若框架需反射访问内部类(如 Jackson 序列化
com.example.config.model),用opens替代exports:opens com.example.config.model to com.fasterxml.jackson.databind; - 依赖声明要精准:
requires java.logging;而不是笼统写requires java.se;;第三方库如 Jackson 若无模块名,会被视为 自动模块(名称来自 JAR 文件名,如jackson-databind-2.15.2.jar→ 模块名jackson.databind)
处理传递性依赖与变量暴露风险
requires transitive 是双刃剑:它让下游模块“自动获得”你所依赖模块的导出包,但也可能无意中把不该暴露的类型透出去:
立即学习“Java免费学习笔记(深入)”;
- 仅当你的 public API 方法签名中直接使用了某依赖模块的类型(如返回
ObjectMapper),才加transitive;否则删掉,避免强耦合 - 删除
public static final全局变量,改用服务提供者接口(SPI)或依赖注入获取配置值,切断隐式变量传递链 - 禁止跨模块
new XxxServiceImpl();所有共享能力必须通过exports的接口 + 工厂类或服务加载器交付
用工具验证解耦是否真正落地
写完 module-info.java 不代表迁移完成。要用三类反馈确认封装生效:
-
编译期报错:把某个 impl 类所在包从
exports移除后,原调用代码应立即编译失败,而不是继续“静默工作” -
jdeps 分析依赖图:
jdeps --module-path mods/ --list-deps myapp.jar查看实际模块依赖,确认没有意外的跨模块字段引用 -
运行时报非法访问:尝试反射读取未
opens的包内public字段,JVM 必须抛IllegalAccessError,而非返回值——这才是模块封装起效的标志


















