java模块化构建高内聚低耦合组件的关键在于:以业务域划分模块,通过module-info.java显式声明exports api包、requires/requires transitive依赖,并用services机制解耦接口与实现,结合di框架实现编译期约束与运行时组装分离。

用Java模块化系统(JPMS)构建高内聚低耦合的独立组件,关键不在于堆砌模块数量,而在于通过显式边界 + 抽象契约 + 依赖约束让每个组件真正“自包含、可替换、少打扰”。核心是把模块当作有边界的“小王国”,而不是带包名的文件夹。
明确模块职责,从业务域而非技术层切分
一个模块应围绕一个清晰的业务能力组织,比如com.example.order负责订单全生命周期,com.example.payment专注支付流程,而不是按Controller/Service/Repository分层建模块。每个模块内部可自行分层,但对外只暴露该能力的API包。
- 在
module-info.java中仅exports语义明确的API包,如exports com.example.order.api; - 绝不导出
impl、internal或config包,哪怕它们当前没被外部调用 - 避免“通用工具模块”——日期格式化、JSON序列化等应由JDK或标准库提供,或封装进具体业务模块内部
用requires和requires transitive精准控制依赖流向
模块依赖不是越多越好,而是要让依赖关系可读、可验、可剪裁。普通requires表示“我需要它”,requires transitive表示“我提供的API会把它的类型暴露给我的调用者”。
- 若
order模块的API方法返回了payment.PaymentResult,则必须声明requires transitive com.example.payment;,否则下游模块编译失败 - 若只是内部调用支付模块做处理,但API不暴露其类型,就只写
requires com.example.payment; - 禁止循环依赖:A requires B,B又requires A —— JPMS编译器会直接报错,这是模块边界的天然守门员
接口定义在提供方模块,实现放在消费方或独立实现模块
高内聚要求行为与数据尽可能靠近,低耦合要求调用方不绑定实现。因此,接口应由能力提供方定义并导出,而具体实现可由其他模块提供,或由提供方自己提供默认实现。
- 例如
com.example.notification模块导出NotificationService接口,但不提供EmailNotificationService实现 - 实际项目中,另建
com.example.notification.email模块实现该接口,并在module-info.java中声明provides NotificationService with EmailNotificationService; - 业务模块(如
order)只需requires com.example.notification;,并通过ServiceLoader.load(NotificationService.class)获取实例,完全不感知实现来源
结合依赖注入框架,隔离运行时组装逻辑
JPMS管编译和类加载边界,DI框架管对象协作关系。两者配合才能真正解耦:模块声明“我能提供什么”,容器决定“谁来提供、何时提供”。
- Spring Boot 3+原生支持JPMS,推荐用构造器注入:
public OrderService(PaymentProcessor processor, NotificationService notifier) - 避免在模块内硬编码
new或静态工厂,所有跨模块协作都走接口+注入 - 测试时可直接传入模拟实现,无需启动容器——模块本身不依赖Spring,只依赖接口契约
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











