模块化系统通过 module-info.java 显式声明依赖与导出,强制“谁用谁声明”,编译期暴露耦合问题;按能力拆分模块、限制 exports、规范 requires;配合 spi 和装配模块解耦 new 硬编码;支持自动模块渐进迁移。

直接用模块化系统梳理依赖,不是给每个类加修饰符,而是从项目结构层面切断隐式耦合。核心是把“谁用谁声明”变成强制规则,让编译期就暴露依赖问题。
用 module-info.java 显式定义边界
在每个业务子模块根目录下新建 module-info.java,明确写出它依赖什么、暴露什么:
- 删掉所有无约束的 import * —— 模块不导出的包,哪怕写了 import 也会编译失败
-
只 exports 真正需要被其他模块调用的 API 包,比如
com.example.order.api,而com.example.order.internal不写 exports 就自动对外不可见 -
requires 必须列全实际用到的模块,包括 JDK 模块(如
requires java.sql;)和自定义模块(如requires com.example.user;)
把“打成一个大 jar”的旧习惯拆开
遗留系统常把 DAO、Service、Model 全塞进同一个 jar,模块化要求你按能力切分:
- 新建
com.example.order模块:只放订单领域逻辑,不包含数据库连接或日志实现 - 新建
com.example.order.persistence模块:只负责 JPA/Hibernate 相关代码,requires com.example.order,但反过来不能依赖 - 新建
com.example.common模块:放通用工具类,被其他模块 requires,但自身不依赖任何业务模块
处理老代码里到处 new 的硬编码依赖
模块化本身不解决运行时对象创建,但能配合设计推动解耦:
- 把
new UserServiceImpl()这类代码移到专门的 服务装配模块 中,该模块 requires 所有实现类所在模块,再通过ServiceLoader或简单工厂返回实例 - 在 module-info.java 中用
uses声明要使用的 SPI 接口,用provides ... with声明实现类,让 JVM 在运行时自动发现 - 避免在业务模块里直接
Class.forName("xxx.impl")—— 这会绕过模块检查,导致运行时报NoClassDefFoundError
渐进迁移时兼容老类路径代码
不能一上来就全模块化,可分三步走:
- 先保留原有 jar 结构,但为每个 jar 添加 module-info.java,命名为 自动模块(如
order-service.jar→ 模块名自动为order.service) - 用
jdeps --module-path扫描现有 jar 之间的隐式依赖,生成初步的 requires 列表 - 逐步将
exports范围收窄,把内部包从 exports 中移除,观察哪些调用方报错,再针对性调整或提供新 API
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











