使用版本目录集中管理依赖版本

使用版本目录提供了一种集中、声明式的方式来管理整个构建中的依赖版本。

解释

当您在单个共享版本目录中定义依赖版本时,可以减少重复并使升级更容易。您无需更改数十个 build.gradle(.kts) 文件,只需在一个地方更新版本。这简化了维护,提高了版本一致性,并降低了模块之间意外版本漂移的风险。项目之间一致的版本声明也使得在测试期间更容易推断行为——尤其是在模块化构建中,因为传递性升级可能会在构建的后期阶段悄悄地改变运行时行为。

然而,版本目录只影响声明的版本,而不影响解析的版本。将它们与依赖锁定版本对齐结合使用,以强制构建之间的一致性。要影响解析的版本,请查看平台

示例

不要这样做

避免在 project.ext、常量或局部变量中声明版本

build.gradle.kts
plugins {
    id("java-library")
    id("com.github.ben-manes.versions").version("0.45.0")
}
val groovyVersion = "3.0.5"

dependencies {
    api("org.codehaus.groovy:groovy:$groovyVersion")
    api("org.codehaus.groovy:groovy-json:$groovyVersion")
    api("org.codehaus.groovy:groovy-nio:$groovyVersion")

    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}

dependencies {
    implementation("org.apache.commons:commons-lang3") {
        version {
            strictly("[3.8, 4.0[")
            prefer("3.9")
        }
    }
}
build.gradle
plugins {
    id('java-library')
    id('com.github.ben-manes.versions').version('0.45.0')
}
def groovyVersion = '3.0.5'

dependencies {
    api("org.codehaus.groovy:groovy:$groovyVersion")
    api("org.codehaus.groovy:groovy-json:$groovyVersion")
    api("org.codehaus.groovy:groovy-nio:$groovyVersion")

    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")

    implementation("org.apache.commons:commons-lang3") {
        version {
            strictly("[3.8, 4.0[")
            prefer("3.9")
        }
    }
}

避免滥用版本目录处理不相关的事务

  • 不要用它们来存储共享字符串或非库常量

  • 不要用任意逻辑或插件特定配置使它们过载

取而代之这样做

gradle/ 目录中使用集中式 libs.versions.toml 文件

gradle/libs.versions.toml
[versions]
groovy = "3.0.5"
junit-jupiter = "5.10.0"

[libraries]
groovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" }
groovy-json = { module = "org.codehaus.groovy:groovy-json", version.ref = "groovy" }
groovy-nio = { module = "org.codehaus.groovy:groovy-nio", version.ref = "groovy" }
commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = { strictly = "[3.8, 4.0[", prefer="3.9" } }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }

[bundles]
groovy = ["groovy-core", "groovy-json", "groovy-nio"]

[plugins]
versions = { id = "com.github.ben-manes.versions", version = "0.45.0" }
build.gradle.kts
plugins {
    id("java-library")
    alias(libs.plugins.versions)
}
dependencies {
    api(libs.bundles.groovy)
    testImplementation(libs.junit.jupiter)
    implementation(libs.commons.lang3)
}
build.gradle
plugins {
    id('java-library')
    alias(libs.plugins.versions)
}
dependencies {
    api(libs.bundles.groovy)
    testImplementation(libs.junit.jupiter)
    implementation(libs.commons.lang3)
}

适当地命名版本目录条目

版本目录中一致且描述性的名称可提高构建脚本的可读性和可维护性。

解释

版本目录提供了一种集中管理依赖项的方式。采用清晰的目录条目命名约定可确保开发人员可以轻松地在整个项目中识别和使用依赖项。

以下准则有助于有效地命名目录条目

  1. 使用连字符分隔段:优先使用连字符(-)而不是下划线(_)来分隔条目名称的不同部分。

    示例:对于 org.apache.logging.log4j:log4j-api,使用 log4j-api

  2. 从项目组派生第一个段:使用项目组 ID 中的唯一标识符作为第一个段。

    示例:对于 com.fasterxml.jackson.core:jackson-databind,使用 jackson-databind

  3. 省略冗余段:如果组 ID 和 artifact ID 相同,请避免重复它们。

    示例:对于 io.ktor:ktor-client-core,使用 ktor-client-core,而不是 ktor-ktor-client-core

  4. 将内部连字符转换为驼峰命名法:如果 artifact ID 包含连字符,请将它们转换为驼峰命名法,以便在代码中更好地阅读。

    示例spring-boot-starter-web 变为 springBootStarterWeb

  5. 避免隐式术语:排除在项目上下文中显而易见或暗示的术语。

    示例:对于 com.amazonaws:aws-java-sdk-core,使用 aws-core,而不是 aws-javaSdkCore

  6. 插件库后缀为 -plugin:当引用插件作为库时(不在 [plugins] 部分),在名称后附加 -plugin

    示例:对于 org.owasp:dependency-check-gradle,使用 dependency-check-plugin

示例

gradle/libs.versions.toml
[versions]
slf4j = "2.0.13"
jackson = "2.17.1"
groovy = "3.0.5"
checkstyle = "8.37"
commonsLang = "3.9"

[libraries]
# SLF4J
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }

# Jackson
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
jackson-dataformatCsv = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-csv", version.ref = "jackson" }

# Groovy bundle
groovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" }
groovy-json = { module = "org.codehaus.groovy:groovy-json", version.ref = "groovy" }
groovy-nio = { module = "org.codehaus.groovy:groovy-nio", version.ref = "groovy" }

# Apache Commons Lang
commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = { strictly = "[3.8, 4.0[", prefer = "3.9" } }

[bundles]
groovy = ["groovy-core", "groovy-json", "groovy-nio"]

[plugins]
versions = { id = "com.github.ben-manes.versions", version = "0.45.0" }
build.gradle.kts
plugins {
    id("java-library")
    alias(libs.plugins.versions)
}

repositories {
    mavenCentral()
}

dependencies {
    // SLF4J
    implementation(libs.slf4j.api)

    // Jackson
    implementation(libs.jackson.databind)
    implementation(libs.jackson.dataformatCsv)

    // Groovy bundle
    api(libs.bundles.groovy)

    // Commons Lang
    implementation(libs.commons.lang3)
}
build.gradle
plugins {
    id 'java-library'
    alias(libs.plugins.versions)
}

repositories {
    mavenCentral()
}

dependencies {
    // SLF4J
    implementation libs.slf4j.api

    // Jackson
    implementation libs.jackson.databind
    implementation libs.jackson.dataformatCsv

    // Groovy bundle
    api libs.bundles.groovy

    // Commons Lang
    implementation libs.commons.lang3
}

在设置文件中设置依赖仓库

settings.gradle.kts 中声明插件和依赖项的仓库。

解释

使用 settings.gradle.kts 文件声明仓库有几个好处

  • 避免重复:集中化仓库声明消除了在每个项目的 build.gradle.kts 中重复声明的需要。

  • 提高可调试性:确保所有项目在解析过程中从相同的仓库以一致的顺序解析依赖项。

  • 与构建模型匹配:仓库不是项目定义的一部分;它们是全局构建逻辑的一部分,因此设置文件是它们更合适的位置。

虽然 dependencyResolutionManagement.repositories 是一个孵化中的 API,但它是声明仓库的首选方式。

示例

不要这样做

您可以在单独的 build.gradle.kts 文件中设置仓库,如下所示

build.gradle.kts
buildscript {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
}

plugins {
    id("java")
}

repositories {
    mavenCentral()
}
build.gradle
buildscript {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
}

plugins {
    id("java")
}

repositories {
    mavenCentral()
}

取而代之这样做

相反,您应该在 settings.gradle.kts 中设置它们,如下所示

settings.gradle.kts
pluginManagement {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
    repositories {
        mavenCentral()
    }
}
settings.gradle
pluginManagement {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
    repositories {
        mavenCentral()
    }
}

不要显式依赖 Kotlin 标准库

Kotlin Gradle 插件会自动将对 Kotlin 标准库(stdlib)的依赖项添加到每个源集,因此无需显式声明它。

解释

添加的标准库版本与应用于项目的 Kotlin Gradle 插件版本相同。如果您的构建不需要特定或不同版本的标准库,则应避免手动添加它。

kotlin.stdlib.default.dependency 属性设置为 false 可防止 Kotlin 插件自动将 Kotlin 标准库依赖项添加到您的项目中。这在特定场景下很有用,例如当您想要手动管理 Kotlin 标准库依赖项版本时。

示例

不要这样做

build.gradle.kts
plugins {
    kotlin("jvm").version("2.0.21")
}

dependencies {
    api(kotlin("stdlib")) (1)
}
build.gradle
plugins {
    id("org.jetbrains.kotlin.jvm") version "2.0.21"
}

dependencies {
    api("org.jetbrains.kotlin:kotlin-stdlib:2.0.21") (1)
}
1 stdlib 被显式依赖:此项目包含对 Kotlin 标准库的隐式依赖,这是编译其源代码所必需的。

这样做

build.gradle.kts
plugins {
    kotlin("jvm").version("2.0.21") (1)
}
build.gradle
plugins {
    id("org.jetbrains.kotlin.jvm") version "2.0.21"  (1)
}
1 不显式包含 stdlib 依赖项:标准库仍然可用,需要它的源代码可以毫无问题地编译。

避免冗余的依赖声明

避免多次声明相同的依赖项,尤其是当它已经通过传递性或通过其他配置可用时。

解释

在 Gradle 构建脚本中重复依赖项可能导致

  • 增加维护成本:在多个地方声明依赖项会使其更难管理。

  • 意外行为:在多个配置中(例如,compileOnlyimplementation)声明相同的依赖项可能导致难以诊断的类路径问题。

示例

不要这样做

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

dependencies {
    api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0") (1)
}
build.gradle
plugins {
    id 'java-library'
}

dependencies {
    api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0") (1)
}
1 implementation 范围中的冗余依赖项。

这样做

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

dependencies {
    api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0") (1)
}
build.gradle
plugins {
    id 'java-library'
}

dependencies {
    api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0") (1)
}
1 声明一次依赖项

使用单个 GAV (group:artifact:version) 字符串声明依赖

在不使用版本目录声明依赖项时,优先使用单个 GAV 字符串表示法 implementation("org.example:library:1.0")。除非必要,避免使用命名参数表示法 implementation(group = "org.example", name = "library", version = "1.0")

命名参数和映射表示法将来可能会被弃用。

解释

当 Gradle 解析依赖项时,所有这些声明都将被同等对待。然而,单字符串形式更简洁,更易于阅读,并且在更广泛的 JVM 生态系统中被广泛采用。

Maven Central 在其文档和使用示例中也推荐这种格式,使其成为开发人员在各种工具中最熟悉和一致的风格。

使用命名参数格式的一个有效理由是,当从 Ivy 仓库解析需要按名称选择特定配置的依赖项时。此语法仅适用于 Ivy 依赖项,不应与标准 Maven 模块一起使用

// Use this syntax only for Ivy modules when a specific configuration must be selected
dependencies {
    implementation(group = "org.someOrg", name = "someModule", version = "1.0", configuration = "someConf")
}

示例

不要这样做

build.gradle.kts
dependencies {
    implementation(group = "com.fasterxml.jackson.core", name = "jackson-databind", version = "32.17.0")  (1)
    api(group = "com.google.guava", name = "guava", version = "32.1.2-jre") {
        exclude(group = "com.google.code.findbugs", module = "jsr305")  (2)
    }
}
build.gradle
dependencies {
    implementation(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.17.0') (1)
    api(group: 'com.google.guava', name: 'guava', version: '32.1.2-jre') {
        exclude(group: 'com.google.code.findbugs', module: 'jsr305')    (2)
    }
}
1 声明依赖项时避免使用命名参数表示法
2 其他修饰符方法和约束(如 exclude)不在此建议中,可以根据需要使用命名参数表示法

这样做

build.gradle.kts
dependencies {
    implementation("com.fasterxml.jackson.core:jackson-databind:2.17.0") (1)
    api("com.google.guava:guava:32.1.2-jre") {
        exclude(group = "com.google.code.findbugs", module = "jsr305")  (2)
    }
}
build.gradle
dependencies {
    implementation('com.fasterxml.jackson.core:jackson-databind:2.17.0') (1)
    api('com.google.guava:guava:32.1.2-jre') {
        exclude(group: 'com.google.code.findbugs', module: 'jsr305')    (2)
    }
}
1 声明依赖项时改用字符串表示法
2 其他修饰符方法和约束(如 exclude)不在此建议中,可以根据需要使用命名参数表示法