必须用requires transitive当模块api签名直接依赖另一模块类型,否则下游编译报错;它仅显式导出构成公共api基础的依赖,如日志、json处理等,而非泛化暴露。

用 requires transitive 实现依赖传递,核心是让下游模块“自动获得”某项能力,无需重复声明。它不是泛泛地“让依赖可见”,而是有明确作用域和设计意图的显式导出——只对真正构成公共 API 基础的部分启用。
什么时候必须用 requires transitive
当你的模块对外提供接口,而这些接口的签名(方法返回值、参数类型、异常类型)或行为逻辑**直接依赖另一个模块的类型**时,就必须导出该依赖。否则,调用方在编译时会报错:找不到符号。
- 日志框架封装模块(如
com.example.logging)被多个业务模块共用 → 它的 API 类型(如Logger)需被下游直接使用 - 通用响应体模块(如
com.example.api)中定义了Response<t></t>,其中泛型T来自com.fasterxml.jackson.core→ JSON 处理能力必须透出 - 工具模块暴露了带
java.time参数的公共方法 → 不需要加transitive(因为java.base已默认导出)
如何正确写 module-info.java
关键在于分清“谁用谁声明”和“谁导出谁负责”。一个模块只对自己直接依赖、且希望被下游间接使用的部分加 transitive。
- 基础库模块(
core)要导出日志能力:module com.example.core {<br> requires transitive com.example.logging;<br> requires java.base;<br>} - 业务模块(
app)只需声明依赖core:module com.example.app {<br> requires com.example.core;<br>}
此时它就能直接 newLogger、调用log.info(),无需再写requires com.example.logging - 若
core还用了com.example.utils但仅作内部辅助(比如私有方法里格式化字符串),则用普通requires即可,不加transitive
避免常见误用陷阱
requires transitive 不是“方便就加上”,滥用会导致依赖污染和版本僵化。
- 不要为测试依赖加
transitive(如junit),它们本就不该出现在运行时类路径 - 不要为实现细节模块加
transitive(如某个仅用于core内部缓存的cache-impl),这会让上层被迫承载无关能力 - 如果两个模块都用到了同一个第三方库,但各自封装方式不同,宁可让上层分别声明,也不要通过中间模块强行
transitive导出——这会掩盖真实依赖关系
验证是否生效的实操方法
光看 module-info.java 不够,要结合编译和运行时行为确认。
- 在
app模块中写一行代码:Logger logger = LoggerFactory.getLogger("test");,不加任何额外requires,能编译通过即表示transitive生效 - 用
jdeps --list-deps --module-path mods/ app.jar查看app的实际依赖图,确认com.example.logging是否出现在其直接依赖列表中 - 故意在
core中移除transitive,再编译app—— 应立刻报错cannot find symbol Logger,这是最直接的反向验证











