依赖项最佳实践
使用版本目录集中管理依赖版本
使用版本目录提供了一种集中、声明式的方式来管理整个构建中的依赖版本。
解释
当您在单个共享版本目录中定义依赖版本时,可以减少重复并使升级更容易。您无需更改数十个 build.gradle(.kts)
文件,只需在一个地方更新版本。这简化了维护,提高了版本一致性,并降低了模块之间意外版本漂移的风险。项目之间一致的版本声明也使得在测试期间更容易推断行为——尤其是在模块化构建中,因为传递性升级可能会在构建的后期阶段悄悄地改变运行时行为。
示例
不要这样做
避免在 project.ext
、常量或局部变量中声明版本
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")
}
}
}
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
文件
[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" }
plugins {
id("java-library")
alias(libs.plugins.versions)
}
dependencies {
api(libs.bundles.groovy)
testImplementation(libs.junit.jupiter)
implementation(libs.commons.lang3)
}
plugins {
id('java-library')
alias(libs.plugins.versions)
}
dependencies {
api(libs.bundles.groovy)
testImplementation(libs.junit.jupiter)
implementation(libs.commons.lang3)
}
适当地命名版本目录条目
版本目录中一致且描述性的名称可提高构建脚本的可读性和可维护性。
解释
版本目录提供了一种集中管理依赖项的方式。采用清晰的目录条目命名约定可确保开发人员可以轻松地在整个项目中识别和使用依赖项。
以下准则有助于有效地命名目录条目
-
使用连字符分隔段:优先使用连字符(
-
)而不是下划线(_
)来分隔条目名称的不同部分。示例:对于
org.apache.logging.log4j:log4j-api
,使用log4j-api
-
从项目组派生第一个段:使用项目组 ID 中的唯一标识符作为第一个段。
示例:对于
com.fasterxml.jackson.core:jackson-databind
,使用jackson-databind
-
省略冗余段:如果组 ID 和 artifact ID 相同,请避免重复它们。
示例:对于
io.ktor:ktor-client-core
,使用ktor-client-core
,而不是ktor-ktor-client-core
-
将内部连字符转换为驼峰命名法:如果 artifact ID 包含连字符,请将它们转换为驼峰命名法,以便在代码中更好地阅读。
示例:
spring-boot-starter-web
变为springBootStarterWeb
-
避免隐式术语:排除在项目上下文中显而易见或暗示的术语。
示例:对于
com.amazonaws:aws-java-sdk-core
,使用aws-core
,而不是aws-javaSdkCore
-
插件库后缀为
-plugin
:当引用插件作为库时(不在[plugins]
部分),在名称后附加-plugin
。示例:对于
org.owasp:dependency-check-gradle
,使用dependency-check-plugin
示例
[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" }
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)
}
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
文件中设置仓库,如下所示
buildscript {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("java")
}
repositories {
mavenCentral()
}
buildscript {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("java")
}
repositories {
mavenCentral()
}
取而代之这样做
相反,您应该在 settings.gradle.kts
中设置它们,如下所示
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
repositories {
mavenCentral()
}
}
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 标准库依赖项版本时。 |
示例
不要这样做
plugins {
kotlin("jvm").version("2.0.21")
}
dependencies {
api(kotlin("stdlib")) (1)
}
plugins {
id("org.jetbrains.kotlin.jvm") version "2.0.21"
}
dependencies {
api("org.jetbrains.kotlin:kotlin-stdlib:2.0.21") (1)
}
1 | stdlib 被显式依赖:此项目包含对 Kotlin 标准库的隐式依赖,这是编译其源代码所必需的。 |
这样做
plugins {
kotlin("jvm").version("2.0.21") (1)
}
plugins {
id("org.jetbrains.kotlin.jvm") version "2.0.21" (1)
}
1 | 不显式包含 stdlib 依赖项:标准库仍然可用,需要它的源代码可以毫无问题地编译。 |
避免冗余的依赖声明
避免多次声明相同的依赖项,尤其是当它已经通过传递性或通过其他配置可用时。
解释
在 Gradle 构建脚本中重复依赖项可能导致
-
增加维护成本:在多个地方声明依赖项会使其更难管理。
-
意外行为:在多个配置中(例如,
compileOnly
和implementation
)声明相同的依赖项可能导致难以诊断的类路径问题。
示例
不要这样做
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)
}
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 范围中的冗余依赖项。 |
这样做
plugins {
`java-library`
}
dependencies {
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0") (1)
}
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")
}
示例
不要这样做
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)
}
}
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 )不在此建议中,可以根据需要使用命名参数表示法 |
这样做
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)
}
}
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 )不在此建议中,可以根据需要使用命名参数表示法 |