Gradle 依赖冲突解决和版本管理

FreeGuideOnline 最新 2026-07-08

bash gradle dependencies


更多实用选项:

```bash
# 查看特定配置(如 compileClasspath)的依赖树
gradle dependencies --configuration compileClasspath

# 将结果输出到文件,便于搜索
gradle dependencies > deps.txt

用 build scan 可视化依赖

如果你使用 Gradle 7+(或 6.x 配合 --scan 选项),可以生成在线报告:

gradle build --scan

报告中的 Dependencies 视图能交互式展开每个变体的依赖树,清晰标注冲突和被升级/降级的依赖。


解决依赖冲突的四大核心手段

1. 强制指定统一版本(force)

最简单粗暴的方式:无论依赖树中出现多少种版本,强制所有路径使用你指定的版本。

// Kotlin DSL (build.gradle.kts)
configurations.all {
    resolutionStrategy {
        force("com.google.guava:guava:31.1-jre")
    }
}

Groovy DSL 写法:

configurations.all {
    resolutionStrategy {
        force 'com.google.guava:guava:31.1-jre'
    }
}

适用场景:你明确知道哪个版本是安全的,且不希望任何传递依赖改变它。
注意:过度使用 force 可能导致将来的依赖更新被隐性覆盖,埋下隐患。

2. 排除传递依赖(exclude)

如果你根本不需要某个传递依赖引入的冲突模块,可以直接排除它。

按组织或模块排除

dependencies {
    implementation("com.example:lib-a:1.0") {
        // 排除整个组织的所有模块
        exclude(group = "com.unwanted.group")
    }
    implementation("com.example:lib-b:2.0") {
        // 排除特定模块
        exclude(group = "commons-logging", module = "commons-logging")
    }
}

全局排除规则(影响所有依赖):

configurations.all {
    exclude(group = "commons-logging", module = "commons-logging")
}

适用场景:已知某个传递依赖完全无用(如旧版 logging 桥),或与你的环境冲突(如 Android 项目中排除 commons-logging,改用 Android 原生)。

3. 依赖替换(dependency substitution)

当一个库被另一个实现了相同 API 的库替代时,可以用替换机制重定向依赖。

configurations.all {
    resolutionStrategy {
        // 将 log4j 替换为 log4j-over-slf4j
        dependencySubstitution {
            substitute(module("log4j:log4j"))
                .using(module("org.slf4j:log4j-over-slf4j:1.7.36"))
        }
        // 使用 project 替换外部依赖(多模块项目)
        dependencySubstitution {
            substitute(module("com.example:my-lib"))
                .using(project(":my-lib"))
        }
    }
}

适用场景:消除版本冲突的同时,还改变了实现的提供方(例如统一日志门面)。

4. 使用约束与平台(platform/BOM)

这是现代 Gradle 推荐的版本管理方式,尤其适合多模块项目。

版本约束(constraints)

在依赖声明中使用 constraints 通知 Gradle 期望的版本范围或精确版本,而不强制:

dependencies {
    implementation("com.google.guava:guava")
    constraints {
        implementation("com.google.guava:guava:31.1-jre") {
            because("we need the new Cache API")
        }
    }
}

constraints 会参与版本冲突的仲裁,但不会主动引入依赖。

BOM(Bill of Materials)平台

使用 Spring Boot、Micronaut 等框架提供的 BOM 统一管理一组库的版本。

dependencies {
    // 导入 Spring Boot BOM
    implementation(platform("org.springframework.boot:spring-boot-dependencies:3.1.0"))
    // 导入多个 BOM 可以叠加,冲突时 Gradle 选择更高版本
    implementation(platform("org.springframework.cloud:spring-cloud-dependencies:2022.0.3"))

    // 声明时无需指定版本,由 BOM 托管
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.cloud:spring-cloud-starter-openfeign")
}

你也可以发布自己的 BOM,请参考 Gradle 的 java-platform 插件。


版本管理的正确姿势:告别“版本地狱”

依赖冲突的根源往往在于版本散乱、难以统一维护。以下方法帮你从源头治理。

集中定义版本属性

gradle.propertiesbuildSrc 中集中管理版本号,避免各处硬编码。

方法 A:gradle.properties

# gradle.properties
GUAVA_VERSION=31.1-jre
RETROFIT_VERSION=2.9.0
// build.gradle.kts
dependencies {
    implementation("com.google.guava:guava:${property("GUAVA_VERSION")}")
}

方法 B:使用 buildSrc 或 included build 的 Version Catalog

Gradle 7.0 开始推荐使用 Version Catalog(libs.versions.toml)。

  1. 在项目根目录创建 gradle/libs.versions.toml
[versions]
guava = "31.1-jre"
retrofit = "2.9.0"

[libraries]
guava = { module = "com.google.guava:guava", version.ref = "guava" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }

[bundles]
networking = ["retrofit", "okhttp"]
  1. 在构建脚本中使用:
dependencies {
    implementation(libs.guava)
    implementation(libs.bundles.networking)
}

这样做的好处是版本号完全集中,且易于跨项目共享。

锁定动态版本与依赖校验

如果你使用了动态版本(如 1.+latest.release),必须锁定依赖以保障构建可重现性。

生成依赖锁文件

// build.gradle.kts
dependencyLocking {
    lockAllConfigurations()
}

执行 gradle dependencies --write-locks 后,会生成 gradle.lockfile 记录精确版本。之后即使存在动态声明,每次构建也会使用锁文件中的版本。
需要更新时执行 gradle dependencies --update-locks <module> 或全部解锁。

依赖校验(Dependency Verification)

Gradle 支持校验下载制品的校验和与签名,防止投毒攻击或意外变更。

# 生成校验文件
gradle --write-verification-metadata sha256,pgp help

生成的 gradle/verification-metadata.xml 中记录所有依赖的校验信息。之后构建时 Gradle 会自动验证。


实战案例:解决 Guava 与 Spring 的版本冲突

假设你的项目直接依赖 Guava 30.0,同时 Spring Boot 2.7 传递依赖 Guava 31.1。依赖报告显示:

\--- org.springframework.boot:spring-boot-starter-web:2.7.5
     \--- com.google.guava:guava:31.1-jre -> 31.1-jre
\--- com.google.guava:guava:30.0-jre -> 31.1-jre

由于默认升到最高版本,看似没问题。但若 31.1 中移除了某个你正在使用的 API,编译就会报错。

解决方案一:降级到兼容版本(force)

configurations.all {
    resolutionStrategy {
        force("com.google.guava:guava:30.0-jre")
    }
}

但要注意,Spring 代码中可能用到了新 API,是否有风险?通过 gradle dependencies 复查 Spring 模块对 Guava 的具体使用,或运行测试验证。

解决方案二:升级自己的代码,适配新版本

直接将你的直接依赖升级到 31.1:

dependencies {
    implementation("com.google.guava:guava:31.1-jre")
}

这样 Gradle 仲裁结果不变,且整个项目统一。这通常是最好的路径。

解决方案三:排除 Spring 中的 Guava,单独提供

极度激进的做法:排除 Spring 引入的 Guava,由你直接控制版本。

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web") {
        exclude(group = "com.google.guava", module = "guava")
    }
    implementation("com.google.guava:guava:30.0-jre")
}