依赖项版本对齐允许属于同一逻辑组(平台)的不同模块在依赖项图中具有相同的版本。

处理不一致的模块版本

Gradle 支持对齐属于同一“平台”的模块的版本。例如,组件的 API 和实现模块最好使用相同的版本。但是,由于传递依赖项解析的游戏,属于同一平台的不同模块最终可能使用不同的版本。例如,您的项目可能依赖于 jackson-databindvert.x 库,如下所示

示例 1. 声明依赖项
build.gradle.kts
dependencies {
    // a dependency on Jackson Databind
    implementation("com.fasterxml.jackson.core:jackson-databind:2.8.9")

    // and a dependency on vert.x
    implementation("io.vertx:vertx-core:3.5.3")
}
build.gradle
dependencies {
    // a dependency on Jackson Databind
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.8.9'

    // and a dependency on vert.x
    implementation 'io.vertx:vertx-core:3.5.3'
}

由于 vert.x 依赖于 jackson-core,因此我们将实际解析以下依赖项版本

  • jackson-core 版本 2.9.5(由 vertx-core 引入)

  • jackson-databind 版本 2.9.5(通过冲突解析)

  • jackson-annotation 版本 2.9.0jackson-databind:2.9.5 的依赖项)

很容易最终得到一套无法很好地协同工作的版本。为了解决这个问题,Gradle 支持依赖项版本对齐,它由平台的概念支持。平台代表一组“协同工作”的模块。要么是因为它们实际上作为一个整体发布(当平台的成员之一发布时,所有其他模块也以相同的版本发布),要么是因为有人测试了这些模块并表明它们协同工作(通常是 Spring 平台)。

使用 Gradle 原生对齐版本

Gradle 原生支持对齐由 Gradle 生成的模块。这是 依赖约束 传递性的直接结果。因此,如果您有一个多项目构建,并且希望消费者获得所有模块的相同版本,Gradle 提供了一种简单的方法,可以使用 Java 平台插件 来实现。

例如,如果您有一个包含 3 个模块的项目

  • lib

  • utils

  • core,依赖于 libutils

以及一个声明以下依赖关系的消费者

  • core 版本 1.0

  • lib 版本 1.1

那么默认情况下,解析将选择 core:1.0lib:1.1,因为 lib 没有依赖于 core。我们可以通过在项目中添加一个新的模块(一个平台)来解决这个问题,该模块将对项目的所有模块添加约束

示例 2. 平台模块
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"))
}

重要的是,平台包含对所有组件的约束,而且每个组件都依赖于平台。通过这样做,每当 Gradle 在图上向平台的模块添加依赖关系时,它也会包含对平台其他模块的约束。这意味着,如果我们看到另一个属于同一平台的模块,我们将自动升级到相同的版本。

在我们的示例中,这意味着我们首先看到 core:1.0,它带来了一个 1.0 平台,其中包含对 lib:1.0lib:1.0 的约束。然后我们添加 lib:1.1,它依赖于 platform:1.1。通过冲突解析,我们选择 1.1 平台,它对 core:1.1 有约束。然后我们对 core:1.0core:1.1 进行冲突解析,这意味着 corelib 现在已正确对齐。

只有在您使用 Gradle 模块元数据时,才会对已发布组件强制执行此行为。

对齐未发布的模块的版本

当发布者没有使用 Gradle 时,例如我们的 Jackson 示例,我们可以向 Gradle 解释所有 Jackson 模块“属于”同一个平台,并享受与原生对齐相同的行为。有两种方法可以表达一组模块属于一个平台。

  1. 平台被发布为一个BOM,并且可以使用:例如,com.fasterxml.jackson:jackson-bom 可以用作平台。在这种情况下,Gradle 缺少的信息是,如果使用平台的成员之一,则应将平台添加到依赖项中。

  2. 无法使用现有的平台。相反,应该由 Gradle 创建一个虚拟平台:在这种情况下,Gradle 会根据所有使用的成员自行构建平台。

为了向 Gradle 提供缺少的信息,您可以定义组件元数据规则,如下所述。

使用已发布的 BOM 对齐模块的版本

build.gradle.kts
abstract class JacksonBomAlignmentRule: ComponentMetadataRule {
    override fun execute(ctx: ComponentMetadataContext) {
        ctx.details.run {
            if (id.group.startsWith("com.fasterxml.jackson")) {
                // declare that Jackson modules belong to the platform defined by the Jackson BOM
                belongsTo("com.fasterxml.jackson:jackson-bom:${id.version}", false)
            }
        }
    }
}
build.gradle
abstract class JacksonBomAlignmentRule implements ComponentMetadataRule {
    void execute(ComponentMetadataContext ctx) {
        ctx.details.with {
            if (id.group.startsWith("com.fasterxml.jackson")) {
                // declare that Jackson modules belong to the platform defined by the Jackson BOM
                belongsTo("com.fasterxml.jackson:jackson-bom:${id.version}", false)
            }
        }
    }
}

通过使用 belongsTo 并将 false虚拟)传递给它,我们声明所有模块都属于同一个已发布平台。在这种情况下,平台是 com.fasterxml.jackson:jackson-bom,Gradle 会像对任何其他模块一样,在声明的存储库中查找它。

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

使用该规则,上述示例中的版本将与所选 com.fasterxml.jackson:jackson-bom 版本定义的版本对齐。在这种情况下,将选择 com.fasterxml.jackson:jackson-bom:2.9.5,因为 2.9.5 是所选模块的最高版本。在该 BOM 中,定义了以下版本并将使用它们:jackson-core:2.9.5jackson-databind:2.9.5jackson-annotation:2.9.0jackson-annotation 的较低版本可能是期望的结果,因为它是 BOM 推荐的版本。

此行为自 Gradle 6.1 起运行可靠。实际上,它类似于一个组件元数据规则,该规则使用 withDependencies 将平台依赖项添加到平台的所有成员。

在没有已发布平台的情况下对齐模块的版本

build.gradle.kts
abstract class JacksonAlignmentRule: ComponentMetadataRule {
    override fun execute(ctx: ComponentMetadataContext) {
        ctx.details.run {
            if (id.group.startsWith("com.fasterxml.jackson")) {
                // declare that Jackson modules all belong to the Jackson virtual platform
                belongsTo("com.fasterxml.jackson:jackson-virtual-platform:${id.version}")
            }
        }
    }
}
build.gradle
abstract class JacksonAlignmentRule implements ComponentMetadataRule {
    void execute(ComponentMetadataContext ctx) {
        ctx.details.with {
            if (id.group.startsWith("com.fasterxml.jackson")) {
                // declare that Jackson modules all belong to the Jackson virtual platform
                belongsTo("com.fasterxml.jackson:jackson-virtual-platform:${id.version}")
            }
        }
    }
}

通过使用 belongsTo 关键字而不带任何参数(平台 **是** 虚拟的),我们声明所有模块都属于同一个虚拟平台,该平台由引擎进行特殊处理。虚拟平台不会从存储库中检索。标识符(在本例中为 com.fasterxml.jackson:jackson-virtual-platform)是构建作者自行定义的。平台的“内容”由 Gradle 通过收集指向同一虚拟平台的所有 belongsTo 语句动态创建。

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

使用此规则,上述示例中的所有版本将对齐到 2.9.5。在这种情况下,jackson-annotation:2.9.5 也会被使用,因为这是我们定义的本地虚拟平台。

对于已发布的平台和虚拟平台,Gradle 允许您通过在平台上指定一个强制依赖项来覆盖平台本身的版本选择。

build.gradle.kts
dependencies {
    // Forcefully downgrade the virtual Jackson platform to 2.8.9
    implementation(enforcedPlatform("com.fasterxml.jackson:jackson-virtual-platform:2.8.9"))
}
build.gradle
dependencies {
    // Forcefully downgrade the virtual Jackson platform to 2.8.9
    implementation enforcedPlatform('com.fasterxml.jackson:jackson-virtual-platform:2.8.9')
}