你可以使用确切版本或版本范围来指定依赖,以定义你的项目可以使用哪些版本

dependencies {
    implementation("org.springframework:spring-core:5.3.8")
    implementation("org.springframework:spring-core:5.3.+")
    implementation("org.springframework:spring-core:latest.release")
    implementation("org.springframework:spring-core:[5.2.0, 5.3.8]")
    implementation("org.springframework:spring-core:[5.2.0,)")
}

理解版本声明

Gradle 支持多种方式来声明版本和范围

版本 示例 注意

确切版本

1.3, 1.3.0-beta3, 1.0-20150201.131010-1

一个特定的版本。

Maven 风格范围

[1.0,), [1.1, 2.0), (1.2, 1.5]

[ ] 表示包含边界;( ) 表示不包含边界。请参阅下方了解更多信息

当上限或下限缺失时,该范围没有上限或下限。

排除上限相当于排除某个前缀。

前缀版本范围

1.+, 1.3.+

只包含与 + 之前部分精确匹配的版本。

将版本声明为 + 且不带任何前缀,将包含任何版本。

latest-status 版本

latest.integration, latest.release

匹配具有指定状态的最高版本。请参阅 ComponentMetadata.getStatus()

Maven SNAPSHOT 版本

1.0-SNAPSHOT, 1.4.9-beta1-SNAPSHOT

表示一个快照版本。

Maven 风格范围

在 Maven 风格中有多种选项来表示边界

  • [] 表示包含边界 → [1.1, 2.0]

  • () 表示不包含边界 → (1.1, 2.0)(1.2, 1.5][1.1, 2.0)

  • 对于不包含下限,可以使用 ] 代替 (]1.2, 1.5] 而不是 (1.2, 1.5]

  • 对于不包含上限,可以使用 [ 代替 )[1.1, 2.0[ 而不是 [1.1, 2.0)

理解版本排序

dependencies {
    implementation("org.springframework:spring-core:1.1") // This is a newer version than 1.a
    implementation("org.springframework:spring-core:1.a") // This is a older version than 1.1
}

版本排序用于

  • 确定某个特定版本是否包含在范围内

  • 在执行冲突解决时(使用“基础版本”)确定哪个版本是最新的。

版本根据以下规则排序

  • 将版本拆分成部分

    • 版本使用字符 [. - _ +] 分割成部分。

    • 包含数字和字母的部分会被进一步分割,例如 1a1 会变成 1.a.1

    • 只比较部分,不比较分隔符,因此 1.a.1, 1-a+1, 1.a-11a1 是等价的。(注意:在冲突解决期间存在例外)。

  • 比较等价部分

    • 数字 vs. 数字: 数值越大,版本越高:1.1 < 1.2

    • 数字 vs. 非数字: 数字部分高于非数字部分:1.a < 1.1

    • 非数字 vs. 非数字: 部分按字母顺序和区分大小写进行比较:1.A < 1.B < 1.a < 1.b

    • 额外的数字部分: 带有额外数字部分(即使是零)的版本更高:1.1 < 1.1.0

    • 额外的非数字部分: 带有额外非数字部分的版本更低:1.1.a < 1.1

  • 特殊非数字部分

    • dev 低于任何其他非数字部分:1.0-dev < 1.0-ALPHA < 1.0-alpha < 1.0-rc

    • rc, snapshot, final, ga, releasesp 高于任何其他字符串部分,顺序如下:1.0-zeta < 1.0-rc < 1.0-snapshot < 1.0-final < 1.0-ga < 1.0-release < 1.0-sp

    • 这些特殊值不区分大小写,并且它们的排序与使用的分隔符无关:1.0-RC-1 == 1.0.rc.1

声明富版本

当你使用简写符号声明版本时,该版本被视为所需版本

build.gradle.kts
dependencies {
    implementation("org.slf4j:slf4j-api:1.7.15")
}
build.gradle
dependencies {
    implementation('org.slf4j:slf4j-api:1.7.15')
}

这意味着最低版本将是 1.7.15,并且可以被引擎乐观地升级。

为了强制使用严格版本,并确保只使用依赖项的指定版本,即使其他版本通常兼容也会被拒绝

build.gradle.kts
dependencies {
    implementation("org.slf4j:slf4j-api") {
        version {
            strictly("[1.7, 1.8[")
            prefer("1.7.25")
        }
    }
}
build.gradle
dependencies {
    implementation('org.slf4j:slf4j-api') {
        version {
            strictly '[1.7, 1.8['
            prefer '1.7.25'
        }
    }
}

Gradle 支持富版本声明模型,允许你结合不同级别的版本特异性。

主要术语,从最强到最弱排列如下:

strictly!!

这是最强的版本声明。任何不匹配此符号的版本都将被排除。如果在声明的依赖项上使用,strictly 可以降级版本。对于传递性依赖项,如果找不到可接受的版本,依赖解析将失败。

支持动态版本。

定义后,它会覆盖任何先前的 require 声明,并清除已在该依赖项上声明的任何先前的 reject

require

这确保所选版本不会低于 require 接受的版本,但可以通过冲突解决使其更高,即使更高版本具有不包含上限。这是直接依赖的默认行为。

支持动态版本。

定义后,它会覆盖任何先前的 strictly 声明,并清除已在该依赖项上声明的任何先前的 reject

prefer

这是最弱的版本声明。它仅在未指定更强的非动态版本时适用。

该术语不支持动态版本,可以补充 strictlyrequire

定义后,它会覆盖任何先前的 prefer 声明,并清除已在该依赖项上声明的任何先前的 reject

此外,还有一个术语不在此层级结构中

reject

该术语指定了模块不接受的版本,如果选择到被拒绝的版本,将导致依赖解析失败。

支持动态版本。

富版本声明通过依赖或约束声明上的 version DSL 方法访问,该方法使你可以访问 MutableVersionConstraint

build.gradle.kts
dependencies {
    implementation("org.slf4j:slf4j-api") {
        version {
            strictly("[1.7, 1.8[")
            prefer("1.7.25")
        }
    }

    constraints {
        add("implementation", "org.springframework:spring-core") {
            version {
                require("4.2.9.RELEASE")
                reject("4.3.16.RELEASE")
            }
        }
    }
}
build.gradle
dependencies {
    implementation('org.slf4j:slf4j-api') {
        version {
            strictly '[1.7, 1.8['
            prefer '1.7.25'
        }
    }

    constraints {
        implementation('org.springframework:spring-core') {
            version {
                require '4.2.9.RELEASE'
                reject '4.3.16.RELEASE'
            }
        }
    }
}

为了强制使用严格版本,你也可以使用 !! 符号

build.gradle.kts
dependencies {
    // short-hand notation with !!
    implementation("org.slf4j:slf4j-api:1.7.15!!")
    // is equivalent to
    implementation("org.slf4j:slf4j-api") {
        version {
           strictly("1.7.15")
        }
    }

    // or...
    implementation("org.slf4j:slf4j-api:[1.7, 1.8[!!1.7.25")
    // is equivalent to
    implementation("org.slf4j:slf4j-api") {
        version {
           strictly("[1.7, 1.8[")
           prefer("1.7.25")
        }
    }
}
build.gradle
dependencies {
    // short-hand notation with !!
    implementation('org.slf4j:slf4j-api:1.7.15!!')
    // is equivalent to
    implementation("org.slf4j:slf4j-api") {
        version {
           strictly '1.7.15'
        }
    }

    // or...
    implementation('org.slf4j:slf4j-api:[1.7, 1.8[!!1.7.25')
    // is equivalent to
    implementation('org.slf4j:slf4j-api') {
        version {
           strictly '[1.7, 1.8['
           prefer '1.7.25'
        }
    }
}

上述符号 [1.7, 1.8[!!1.7.25 等价于

  • strictly [1.7, 1.8[

  • prefer 1.7.25

这意味着引擎必须选择一个在 1.7(包含)和 1.8(不包含)之间的版本。如果在依赖图中没有其他组件需要不同版本,它应该优先选择 1.7.25

严格版本不能升级,并会覆盖任何传递性依赖版本,因此建议将范围与严格版本结合使用。

下表说明了几种用例

该依赖项的哪些版本可接受? strictly require prefer rejects 选择结果

已用版本 1.5 测试;认为未来所有版本都应正常工作。

1.5

1.5 开始的任何版本,相当于 org:foo:1.5。接受升级到 2.4

已用 1.5 测试,软约束根据语义版本控制进行升级。

[1.0, 2.0[

1.5

1.02.0 之间的任何版本,如果没其他人关心则选择 1.5。接受升级到 2.4
🔒

已用 1.5 测试,但遵循语义版本控制。

[1.0, 2.0[

1.5

1.02.0 之间的任何版本(不包含),如果没其他人关心则选择 1.5
覆盖传递性依赖的版本。
🔒

与上同,已知 1.4 有问题。

[1.0, 2.0[

1.5

1.4

1.02.0 之间的任何版本(不包含),但排除 1.4,如果没其他人关心则选择 1.5
覆盖传递性依赖的版本。
🔒

无意见,与 1.5 兼容。

1.5

如果无其他意见则选择 1.5,否则选其他版本。

无意见,优先选择最新发布版本。

latest.release

构建时最新的发布版本。
🔒

处于边缘,最新发布版本,不降级。

latest.release

构建时最新的发布版本。
🔒

除 1.5 外无其他版本。

1.5

1.5,如果存在其他 strict 或更高优先级的 require 约束不一致则失败。
覆盖传递性依赖的版本。

仅限 1.5 或其一个补丁版本。

[1.5,1.6[

最新的 1.5.x 补丁版本,如果存在其他 strict 或更高优先级的 require 约束不一致则失败。
覆盖传递性依赖的版本。
🔒

用锁 (🔒) 标注的行表示建议利用依赖锁定的情况。注意:使用依赖锁定时,始终建议发布解析后的版本

在库中使用 strictly 需要仔细考虑,因为它会影响下游使用者。但是,如果使用得当,它可以帮助使用者了解哪些库组合在他们的上下文中可能不兼容。更多详情,请参阅关于覆盖依赖版本的部分。

富版本信息在 Gradle 模块元数据格式中得到保留。但是,将此信息转换为 Ivy 或 Maven 元数据格式时会丢失信息。最高优先级的版本声明——strictlyrequire(优先于 prefer)将被发布,而任何 reject 都将被忽略。

强调严格版本

Gradle 通过选择依赖图中最新的版本来解决任何依赖版本冲突。某些项目可能需要偏离默认行为,强制使用依赖项的更早版本,例如如果项目的源代码依赖于某个依赖项的旧 API,而一些外部库依赖于新 API。

通常,强制依赖用于降级依赖项。降级依赖项的常见用例包括:

  • 在最新发布版本中发现了一个错误。

  • 你的代码依赖于与新版本不二进制兼容的旧版本。

  • 你的代码不使用该库中需要较新版本的部分。

强制使用某个依赖版本需要仔细考虑,因为更改传递性依赖的版本可能会导致外部库期望不同版本而引发运行时错误。如果可能的话,通常最好升级你的源代码以与新版本兼容。

假设一个项目使用HttpClient进行 HTTP 调用。HttpClient 会引入 Commons Codec 作为版本为 1.10 的传递性依赖。然而,该项目的生产源代码需要 Commons Codec 1.9 中的一个 API,而该 API 在 1.10 中已不再可用。可以在构建脚本中将其声明为 strict 来强制使用该依赖版本。

build.gradle.kts
dependencies {
    implementation("org.apache.httpcomponents:httpclient:4.5.4")
    implementation("commons-codec:commons-codec") {
        version {
            strictly("1.9")
        }
    }
}
build.gradle
dependencies {
    implementation 'org.apache.httpcomponents:httpclient:4.5.4'
    implementation('commons-codec:commons-codec') {
        version {
            strictly '1.9'
        }
    }
}

使用严格版本的后果

使用严格版本必须仔细考虑

  • 对于库作者: 严格版本实际上就像强制版本。它们优先于传递性依赖,并覆盖传递性发现的任何其他严格版本。如果消费者项目需要不同版本,这可能导致构建失败。

  • 对于消费者: 严格版本在解析期间被视为全局约束。如果严格版本与消费者的版本要求冲突,将触发解析错误。

例如,如果项目 B strictly 依赖于 C:1.0,而消费者项目 A 需要 C:1.1,则会发生解析错误。

为了避免这种情况,建议使用版本范围并在这些范围内指定一个优先版本。

例如,B 可以说,它严格依赖[1.0, 2.0[ 范围,但优先选择 1.0,而不是 strictly 1.0。这样,如果消费者选择了 1.1(或范围内的任何其他版本),构建将不再失败

不声明版本

对于大型项目,建议不声明依赖的版本,而是使用平台来管理版本

build.gradle.kts
dependencies {
    implementation("org.springframework:spring-web")
}

dependencies {
    constraints {
        implementation("org.springframework:spring-web:5.0.2.RELEASE")
    }
}
build.gradle
dependencies {
    implementation 'org.springframework:spring-web'
}

dependencies {
    constraints {
        implementation 'org.springframework:spring-web:5.0.2.RELEASE'
    }
}

这种方法集中管理版本,包括传递性依赖。

声明动态版本

在许多情况下,你可能需要使用特定模块依赖的最新版本或某个版本范围内的最新版本。这在开发过程中或创建需要兼容各种依赖版本的库时经常需要。项目可能采用更积极的方式来使用依赖项,通过始终集成最新版本来访问尖端特性。

你可以通过使用动态版本轻松管理这些不断变化的依赖项。动态版本可以是版本范围(例如 2.+)或最新可用版本的占位符(例如 latest.integration

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

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework:spring-web:5.+")
}
build.gradle
plugins {
    id 'java-library'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework:spring-web:5.+'
}

使用动态版本和可变模块可能导致构建不可重现。随着模块新版本的发布,其 API 可能与你的源代码不兼容。因此,请谨慎使用此特性。

为了实现可重现的构建,在使用动态版本声明依赖项时,使用依赖锁定至关重要。否则,你请求的模块可能会发生变化,即使版本号相同,这就是所谓的可变版本。例如,Maven SNAPSHOT 模块始终指向最新发布的 artifact,使其成为“可变模块”。

声明可变版本

团队在发布应用程序或库的新版本之前,可能会实现一系列特性。一种允许使用者尽早集成未完成 artifact 的常见策略是发布带有可变版本的模块。可变版本表示特性集仍在积极开发中,尚未发布稳定版本供普遍使用。

在 Maven 仓库中,可变版本通常被称为快照版本。快照版本包含后缀 -SNAPSHOT

以下示例演示如何在 Spring 依赖项上声明快照版本

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

repositories {
    mavenCentral()
    maven {
        url = uri("https://repo.spring.io/snapshot/")
    }
}

dependencies {
    implementation("org.springframework:spring-web:5.0.3.BUILD-SNAPSHOT")
}
build.gradle
plugins {
    id 'java-library'
}

repositories {
    mavenCentral()
    maven {
        url = 'https://repo.spring.io/snapshot/'
    }
}

dependencies {
    implementation 'org.springframework:spring-web:5.0.3.BUILD-SNAPSHOT'
}

Gradle 足够灵活,可以将任何版本视为可变版本。你只需将属性 ExternalModuleDependency.setChanging(boolean) 设置为 true

文件依赖版本化

建议在使用文件依赖时清晰地表达意图并指定具体版本。

文件依赖不被 Gradle 的版本冲突解决考虑。因此,给文件名赋予版本对于指示每个发布版本包含的不同变更集至关重要。

例如,使用 commons-beanutils-1.3.jar 可以通过其发布说明追踪库中的变更。

遵循此做法可以

  • 项目依赖更容易维护和组织。

  • 通过指定的版本更容易识别潜在的 API 不兼容性。