依赖版本对齐确保属于同一逻辑组(一个 平台)的不同模块在依赖图中使版本一致。

为什么会出现模块版本不一致?

Gradle 支持对齐属于同一平台的模块版本。例如,组件的 API 和实现模块应该使用相同的版本

然而,由于传递性依赖解析,同一平台内的模块最终可能会使用不同的版本,从而导致潜在的兼容性问题。

考虑以下示例,你的项目同时依赖于 jackson-databindvert.x

build.gradle.kts
dependencies {
    implementation("com.fasterxml.jackson.core:jackson-databind:2.8.9")
    implementation("io.vertx:vertx-core:3.5.3")
}
build.gradle
dependencies {
    implementation("org.apache.commons:commons-lang3:3.2")

    constraints {
        implementation("org.apache.commons:commons-lang3:3.1") {
            because("Version 1.3 introduces breaking changes not yet handled")
        }
    }
}

依赖解析可能导致

  • jackson-core2.9.5 (vertx-core 所需)

  • jackson-databind2.9.5 (通过冲突解决得到)

  • jackson-annotations2.9.0 (jackson-databind:2.9.5 的依赖)

问题在于 Vert.x (3.5.0) 使用的是旧版本 Jackson (2.9.0),但显式依赖 (2.9.5) 强制 Gradle 将 Vert.xJackson 依赖从 2.9.0 升级到 2.9.5

dependencies.out
> Task :dependencies

------------------------------------------------------------
Root project 'how_to_align_dependency_versions'
------------------------------------------------------------

runtimeClasspath - Runtime classpath of source set 'main'.
+--- com.fasterxml.jackson.core:jackson-databind:2.8.9 -> 2.9.5
|    +--- com.fasterxml.jackson.core:jackson-annotations:2.9.0
|    \--- com.fasterxml.jackson.core:jackson-core:2.9.5
\--- io.vertx:vertx-core:3.5.3
     +--- io.netty:netty-common:4.1.19.Final
     ...
     |    \--- io.netty:netty-transport:4.1.19.Final (*)
     +--- com.fasterxml.jackson.core:jackson-core:2.9.5
     \--- com.fasterxml.jackson.core:jackson-databind:2.9.5 (*)

这种不匹配可能导致兼容性问题和意外失败。

Gradle 通过平台提供依赖版本对齐,确保相关模块使用一致的版本

选项 1:使用已发布的平台

如果存在公共平台(也称为 BOM),可以将其导入为平台

build.gradle.kts
dependencies {
    implementation(platform("com.fasterxml.jackson:jackson-bom:2.8.9"))
    implementation("com.fasterxml.jackson.core:jackson-databind:2.8.9")
    implementation("io.vertx:vertx-core:3.5.3")
}
build.gradle

运行 ./gradlew dependencies --configuration runtimeClasspath 可以展示对齐后的依赖

dependencies-bom.out
> Task :dependencies

------------------------------------------------------------
Root project 'how_to_align_dependency_versions'
------------------------------------------------------------

runtimeClasspath - Runtime classpath of source set 'main'.
+--- com.fasterxml.jackson:jackson-bom:2.8.9
|    +--- com.fasterxml.jackson.core:jackson-databind:2.8.9 -> 2.9.5 (c)
|    +--- com.fasterxml.jackson.core:jackson-core:2.8.9 -> 2.9.5 (c)
|    \--- com.fasterxml.jackson.core:jackson-annotations:2.8.0 -> 2.9.0 (c)
+--- com.fasterxml.jackson.core:jackson-databind:2.8.9 -> 2.9.5
|    +--- com.fasterxml.jackson.core:jackson-annotations:2.9.0
|    \--- com.fasterxml.jackson.core:jackson-core:2.9.5
\--- io.vertx:vertx-core:3.5.3
     +--- io.netty:netty-common:4.1.19.Final
     ...
     |    \--- io.netty:netty-transport:4.1.19.Final (*)
     +--- com.fasterxml.jackson.core:jackson-core:2.9.5
     \--- com.fasterxml.jackson.core:jackson-databind:2.9.5 (*)

选项 2:创建虚拟平台

如果不存在公共 BOM,你可以创建一个虚拟平台

在这种情况下,Gradle 会根据正在使用的模块动态地构建平台。为此,你必须定义组件元数据规则

build.gradle.kts
abstract class JacksonAlignmentRule : ComponentMetadataRule {
    override fun execute(ctx: ComponentMetadataContext) {
        ctx.details.run {
            if (id.group.startsWith("com.fasterxml.jackson")) {
                belongsTo("com.fasterxml.jackson:jackson-virtual-platform:${id.version}")
            }
        }
    }
}

dependencies {
    implementation("com.fasterxml.jackson.core:jackson-databind:2.8.9")
    implementation("io.vertx:vertx-core:3.5.3")

    dependencies {
        components.all<JacksonAlignmentRule>()
    }
}
build.gradle

这确保所有 Jackson 模块对齐到同一版本,即使是传递性引入的。

运行 ./gradlew dependencies --configuration runtimeClasspath 可以展示对齐后的依赖

dependencies-platform.out
> Task :dependencies

------------------------------------------------------------
Root project 'how_to_align_dependency_versions'
------------------------------------------------------------

runtimeClasspath - Runtime classpath of source set 'main'.
+--- com.fasterxml.jackson.core:jackson-databind:2.8.9 -> 2.9.5
|    +--- com.fasterxml.jackson.core:jackson-annotations:2.9.0 -> 2.9.5
|    \--- com.fasterxml.jackson.core:jackson-core:2.9.5
\--- io.vertx:vertx-core:3.5.3
     +--- io.netty:netty-common:4.1.19.Final
     ...
     |    \--- io.netty:netty-transport:4.1.19.Final (*)
     +--- com.fasterxml.jackson.core:jackson-core:2.9.5
     \--- com.fasterxml.jackson.core:jackson-databind:2.9.5 (*)

选项 3:使用 Java 插件

Gradle 原生支持版本对齐,通过使用Java 平台插件

当项目有多个一起版本化的模块(例如 libutilscore)时,使用混合版本(例如 core:1.0lib:1.1)可能导致运行时问题或不兼容。

考虑一个包含三个模块项目

  • lib

  • utils

  • core (依赖于 libutils

一个消费者项目声明

  • core 版本 1.0

  • lib 版本 1.1

默认情况下,Gradle 会选择 core:1.0lib:1.1,导致版本不对齐

要解决此问题,引入一个强制约束的平台模块

build.gradle.kts
plugins {
    `java-platform`
}

dependencies {
    // The platform declares constraints on all components that
    // require alignment
    constraints {
        api(project(":core"))
        api(project(":lib"))
        api(project(":utils"))
    }
}
build.gradle
plugins {
    id 'java-platform'
}

dependencies {
    // The platform declares constraints on all components that
    // require alignment
    constraints {
        api(project(":core"))
        api(project(":lib"))
        api(project(":utils"))
    }
}

每个模块都应声明对平台的依赖

build.gradle.kts
dependencies {
    // Each project has a dependency on the platform
    api(platform(project(":platform")))

    // And any additional dependency required
    implementation(project(":lib"))
    implementation(project(":utils"))
}
build.gradle
dependencies {
    // Each project has a dependency on the platform
    api(platform(project(":platform")))

    // And any additional dependency required
    implementation(project(":lib"))
    implementation(project(":utils"))
}

这确保所有依赖(corelibutils)一致解析到版本 1.1

总结

使用平台和 BOM,Gradle 确保一致的依赖版本,避免兼容性问题。当没有已发布的 BOM 时,虚拟平台允许手动对齐依赖。