核心是用requires transitive精准控制依赖传递、仅exports公共api包、将契约抽离为独立common-api模块。若api返回c类型,则a需requires transitive c;仅内部使用则仅requires;不exports impl包,禁止new实现类或反射访问私有类。

Java 模块化项目中避免模块间紧耦合,核心是让依赖关系清晰、可控、单向,同时限制实现细节的暴露。不是靠“少写 import”来解耦,而是通过模块系统本身的语义和设计习惯主动约束。
用 requires transitive 精准控制依赖传递
模块 A 依赖模块 B,B 又用了模块 C —— 如果 C 是 A 的 API 合约一部分(比如 A 的 public 方法返回了 C 中的类型),那 A 必须让下游看到 C,否则调用方会编译失败。这时应在 A 的 module-info.java 中声明:
-
requires transitive com.fasterxml.jackson.core;—— 表示该依赖对 A 的使用者也可见 -
requires java.sql;—— 若只是内部工具类使用,则不加transitive,避免污染下游模块图
滥用 transitive 会导致隐式依赖蔓延;不用则可能让下游重复声明、破坏封装边界。关键看:这个依赖是否出现在你导出的 API 签名里。
只 exports 明确需要被外部使用的包
一个模块默认不对外暴露任何包。即使你写了 requires,若没 exports,其他模块就无法访问你的类。这不是限制,而是保护:
-
exports com.example.service;—— 允许外部使用接口和 DTO - 不 exports
com.example.service.impl或com.example.internal—— 把实现类、工具类、配置类全关在模块内 - 必要时用
exports ... to moduleA,moduleB做白名单限定,进一步收窄可见范围
很多紧耦合源于“顺手把 impl 包也 export 了”,结果别人直接 new 实现类,一改就崩。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
把共享契约提前抽成独立基础模块
当模块 X 和模块 Y 都要操作同一组模型或事件,别让它们互相依赖,更不要各自定义一套相似的类。正确做法是:
- 新建
common-api模块,只包含接口、DTO、枚举、常量等纯契约内容 - X 和 Y 都
requires transitive common-api - X 提供实现,Y 消费服务 —— 它们之间不再有直接依赖,只通过
common-api交互
这相当于把“协议”从“实现”里剥离开,是松耦合最有效的物理隔离手段。
禁止跨模块直接调用实现类或使用 new
模块化不是换个 module-info.java 就自动解耦。常见反模式包括:
- 模块 A 的代码里写
new BServiceImpl()—— 违反了面向接口原则,也绕过了依赖注入机制 - A 直接 import 并使用 B 的
internal.util.XxxHelper类 —— 即使 B exports 了,也不代表它承诺兼容 - 用反射强行访问另一个模块的私有类或方法 —— 模块系统明确禁止,运行时报
IllegalAccessError
真正解耦的代码,模块 A 里只能看到接口类型(来自 exports 的包),具体实现由框架或工厂提供,A 不掌握也不关心是谁造的。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










