核心是划清可见性边界:requires transitive仅用于api签名中暴露的第三方类型,如variableclient返回jsonnode则需requires transitive jackson;内部实现依赖(如guava限流)不可加transitive,避免污染下游。

要构建一个真正可用的一站式变量基础依赖模块,核心不是堆功能,而是划清“谁该看到什么”。requires transitive就是那把刻刀——它不决定你用不用某个库,而决定下游模块要不要、能不能理解你暴露出去的类型。
只对API签名里出现的第三方类型加 transitive
变量模块对外提供的接口,比如 VariableClient.get(String key),如果返回的是 JsonNode,那 Jackson 就不是你内部的实现细节,而是契约的一部分。下游调用方必须能识别这个类型,否则编译直接失败。
- ✅ 正确:方法返回
com.fasterxml.jackson.databind.JsonNode→requires transitive com.fasterxml.jackson.databind; - ✅ 正确:公开方法参数是
org.slf4j.Logger→requires transitive org.slf4j; - ❌ 错误:仅在内部日志工具类里用
LoggerFactory,但没对外暴露任何 Logger 类型 → 不该加 transitive
分层模块设计 + 传递依赖链要清晰
一个健壮的变量 SDK 不该是一个大而全的单模块,而应拆成三层,并靠 transitive 串起能力边界:
-
core 层:处理加密、缓存、本地 fallback。它用 Jackson 解析配置,但所有 API 都只返回
String或自定义VariableValue—— 所以 Jackson 只需普通requires,不透出。 -
adapter 层(如 nacos、apollo):封装后端通信逻辑。若它的某个公共方法返回
io.nacos.api.config.ConfigService,那就必须requires transitive io.nacos.api.config; -
api 层:导出
VariableClient接口,组合 core 和 adapter 能力。它声明requires transitive com.example.var.core;和requires transitive com.example.var.adapter.nacos;,让使用者一键获得完整能力栈,无需自己补依赖。
避免 transitive 污染:内部依赖绝不外泄
模块里总有些“悄悄干活”的工具类,比如用 Guava 的 RateLimiter 做本地限流,或用 Apache Commons 的 StringUtils 做空值判断。这些类型从不进入 public 方法签名,也不出现在 Javadoc 示例中。
- 它们只是实现细节,不是契约 —— 对应的依赖写
requires com.google.common;即可。 - 一旦错写成
requires transitive,下游项目就会被强制拉入 Guava,哪怕它根本不用限流,也得面对版本冲突风险。 - 测试依赖(如 JUnit、Mockito)更不能加 transitive,它们只应在 test classpath 生效。
导出包与传递依赖必须协同生效
exports 控制“谁能访问我的类”,requires transitive 控制“谁能看到我依赖的类”。两者缺一不可:
- 只
exports com.example.var.api;但没对 Jackson 加 transitive → 下游能 import 接口,但编译时找不到JsonNode,报错。 - 加了
requires transitive com.fasterxml.jackson.databind;但忘了exports com.example.var.api;→ 下游连接口都看不到,更别说用了。 - 正确姿势:接口包导出 + 所有接口中涉及的第三方类型对应模块加 transitive → 使用者才能开箱即用。











