直接声明版本仅约束显式依赖,不控制传递依赖;constraints通过strictly实现全图强制版本仲裁,解决多模块版本冲突与运行时不一致问题。

直接声明版本会覆盖所有传递路径,但不控制间接依赖的版本决议
当你在 dependencies 块里写 implementation 'commons-codec:commons-codec:1.9',这个版本只约束你**直接声明**的这条依赖。如果另一个库(比如 httpclient)内部依赖了 commons-codec:1.10,Gradle 默认按“最新版本胜出”规则选 1.10——你的 1.9 会被忽略,除非你额外加 force = true 或改用严格版本。
这种写法的问题在于:它只解决“我主动写的那行”,对整个依赖图没全局约束力。一旦项目变大、模块增多,很容易出现某处漏写版本,或者某第三方库悄悄升级了传递依赖,导致运行时行为不一致。
依赖约束(constraints)是声明式治理,作用于整个依赖图
constraints 不参与实际依赖拉取,只提供“版本仲裁规则”。它像一张白名单,告诉 Gradle:“不管哪个路径引入 commons-codec,最终都必须落到 1.9”。即使 httpclient 声明要 1.10,只要你在 constraints 里写了 commons-codec 的严格约束,Gradle 就会强制降级并应用该版本。
常见误用点:
- 把
constraints当成普通依赖写进dependencies—— 它必须独立声明在dependencies块外或内嵌使用constraintsDSL - 忘记加
strictly—— 比如只写version '1.9',它只是建议,不是强制;必须用version { strictly '1.9' } - 约束了不存在的坐标 —— 比如拼错
group或name,Gradle 完全无视,不会报错也不会生效
什么时候该用 constraints 而不是直接写版本?
适合以下场景:
- 多模块项目中统一管控基础组件版本(如
slf4j、junit、guava),避免各模块各自声明导致冲突 - 你没直接依赖某个库,但它被多个传递路径带入,且你需要锁定其行为(例如
commons-codec因 API 兼容性必须用1.9) - 想保留依赖声明的“干净性”——主
dependencies块里不写版本,所有版本集中管理在constraints或versions.gradle中
反例:只在一个模块里临时 fix 某个 bug,用 force 或 strictly 直接写在该模块的 dependencies 里更轻量,没必要动全局约束。
strictly 和 force 的关键区别在哪?
force = true 是旧版 Gradle 的写法,已在 Gradle 8+ 中标记为过时;strictly 是当前推荐方式,语义更清晰:它表示“此版本不可协商”,一旦与其他 strictly 冲突(比如两个模块分别约束了 commons-codec:1.9 和 1.10),构建直接失败,不自动取最新。
而 force 是无条件覆盖,不检查冲突,容易掩盖问题。例如:
dependencies {
implementation('commons-codec:commons-codec') {
version { strictly '1.9' }
}
implementation('org.apache.httpcomponents:httpclient') {
version { strictly '4.5.4' }
}
}
如果 httpclient:4.5.4 内部硬编码依赖 commons-codec:1.10,Gradle 会拒绝解析并报错,逼你手动确认兼容性——这才是你真正需要的信号。
约束不是银弹。它让版本决策显性化,但也要求你更清楚每个约束的实际影响范围。最容易被忽略的是:约束只对“能被解析到”的依赖生效;如果某传递依赖被 exclude 掉了,约束就完全不起作用。











