在很多情况下,您希望使用特定模块依赖项的最新版本,或版本范围内的最新版本。这可能是开发过程中的要求,或者您可能正在开发一个旨在与一系列依赖项版本配合使用的库。您可以通过使用动态版本轻松依赖于这些不断变化的依赖项。动态版本可以是版本范围(例如 2.+),也可以是最新可用版本的占位符,例如 latest.integration

或者,您请求的模块甚至可以随着时间的推移而改变,即使是对于相同的版本,即所谓的更改版本。这种类型的更改模块的一个示例是 Maven SNAPSHOT模块,它始终指向已发布的最新工件。换句话说,标准 Maven 快照是一个不断发展的模块,它是一个“更改模块”。

使用动态版本和更改模块会导致构建不可重复。随着特定模块的新版本发布,其 API 可能会与您的源代码不兼容。谨慎使用此功能!

声明动态版本

项目可能会采用更激进的方法来使用模块依赖项。例如,您可能希望始终集成依赖项的最新版本,以随时使用前沿功能。动态版本允许解析给定模块的最新版本或版本范围的最新版本。

在构建中使用动态版本有可能会破坏构建。一旦发布包含不兼容 API 更改的依赖项新版本,您的源代码可能会停止编译。
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.+'
}

构建扫描可以有效地可视化动态依赖项版本及其各自所选版本。

dependency management dynamic dependency build scan
图 1. 构建扫描中的动态依赖项

默认情况下,Gradle 会缓存依赖项的动态版本 24 小时。在此时间范围内,Gradle 不会尝试从声明的存储库中解析较新版本。可以根据需要配置阈值,例如,如果您想更早地解析新版本。

声明更改版本

团队可能会决定在发布应用程序或库的新版本之前实施一系列功能。允许使用者尽早且频繁地集成其工件的未完成版本的一种常见策略是发布具有所谓更改版本的模块。更改版本表示功能集仍处于积极开发中,尚未发布可供一般使用的稳定版本。

在 Maven 存储库中,更改版本通常称为 快照版本。快照版本包含后缀 -SNAPSHOT。以下示例演示如何在 Spring 依赖项上声明快照版本。

示例 2. 声明具有更改版本 的依赖项
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 会缓存依赖项的更改版本 24 小时。在此时间范围内,Gradle 不会尝试从声明的存储库中解析较新版本。可以根据需要 配置阈值,例如,如果您想更早解析新的快照版本。

Gradle 足够灵活,可以将任何版本视为更改版本,例如,如果您想为 Ivy 模块建模快照行为。您需要做的就是将属性 ExternalModuleDependency.setChanging(boolean) 设置为 true

控制动态版本缓存

默认情况下,Gradle 会缓存动态版本和更改模块 24 小时。在此时间范围内,Gradle 不会联系任何声明的远程存储库以获取新版本。如果您希望 Gradle 更频繁地或在每次执行构建时检查远程存储库,那么您需要更改生存时间 (TTL) 阈值。

对动态版本或更改版本使用较短的 TTL 阈值可能会由于 HTTP(s) 调用次数增加而导致构建时间延长。

您可以使用 命令行选项 覆盖默认缓存模式。您还可以使用解析策略在构建程序中 更改缓存过期时间

以编程方式控制依赖项缓存

您可以使用配置的 ResolutionStrategy 以编程方式微调缓存的某些方面。如果您希望永久更改设置,则以编程方式进行很有用。

默认情况下,Gradle 会缓存动态版本 24 小时。要更改 Gradle 缓存动态版本的已解析版本的时间,请使用

build.gradle.kts
configurations.all {
    resolutionStrategy.cacheDynamicVersionsFor(10, "minutes")
}
build.gradle
configurations.all {
    resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes'
}

默认情况下,Gradle 会缓存更改模块 24 小时。要更改 Gradle 缓存更改模块的元数据和工件的时间,请使用

build.gradle.kts
configurations.all {
    resolutionStrategy.cacheChangingModulesFor(4, "hours")
}
build.gradle
configurations.all {
    resolutionStrategy.cacheChangingModulesFor 4, 'hours'
}

从命令行控制依赖项缓存

通过离线模式避免网络访问

--offline 命令行开关告诉 Gradle 始终使用缓存中的依赖项模块,无论是否需要再次检查它们。在离线运行时,Gradle 绝不会尝试访问网络以执行依赖项解析。如果依赖项缓存中没有所需的模块,则构建执行将失败。

刷新依赖项

您可以从命令行控制对特定构建调用的依赖项缓存行为。命令行选项有助于为构建的单次执行做出有选择性的临时选择。

有时,Gradle 依赖项缓存可能与配置的存储库的实际状态不同步。也许存储库最初配置错误,或者也许“不变”模块发布不正确。要刷新依赖项缓存中的所有依赖项,请在命令行上使用 --refresh-dependencies 选项。

--refresh-dependencies 选项告诉 Gradle 忽略已解析模块和工件的所有缓存条目。将针对所有已配置的存储库执行新的解析,重新计算动态版本,刷新模块并下载工件。但是,在可能的情况下,Gradle 会在再次下载之前检查以前下载的工件是否有效。这是通过将存储库中发布的 SHA1 值与现有已下载工件的 SHA1 值进行比较来完成的。

  • 动态依赖项的新版本

  • 更改模块的新版本(使用相同版本字符串但内容可能不同的模块)

刷新依赖项将导致 Gradle 使其列表缓存无效。但是

  • 它将在元数据文件上执行 HTTP HEAD 请求,但如果它们相同,不会重新下载它们

  • 它将在工件文件上执行 HTTP HEAD 请求,但如果它们相同,不会重新下载它们

换句话说,刷新依赖项在您实际使用动态依赖项您有不知道的更改依赖项(在这种情况下,您有责任将它们正确声明为 Gradle 中的更改依赖项)时才有影响。

认为使用 --refresh-dependencies 将强制下载依赖项是一种常见的误解。并非如此:Gradle 仅执行刷新动态依赖项所需的严格操作。这可能涉及下载新的列表或元数据文件,甚至工件,但如果没有更改,影响最小。

使用组件选择规则

当多个版本与版本选择器匹配时,组件选择规则可能会影响应选择哪个组件实例。规则应用于每个可用版本,并允许通过规则明确拒绝该版本。这允许 Gradle 忽略不满足规则设置的条件的任何组件实例。示例包括

  • 对于1.+等动态版本,某些版本可能会被明确拒绝选择。

  • 对于1.4等静态版本,实例可能会基于额外的组件元数据(如 Ivy 分支属性)被拒绝,从而允许使用来自后续存储库的实例。

规则通过 ComponentSelectionRules 对象进行配置。配置的每个规则都将以 ComponentSelection 对象作为参数进行调用,其中包含有关正在考虑的候选版本的信息。调用 ComponentSelection.reject(java.lang.String) 会导致明确拒绝给定的候选版本,在这种情况下,候选版本将不被选择器考虑。

以下示例显示了一个规则,该规则不允许某个模块的特定版本,但允许动态版本选择下一个最佳候选版本。

build.gradle.kts
configurations {
    create("rejectConfig") {
        resolutionStrategy {
            componentSelection {
                // Accept the highest version matching the requested version that isn't '1.5'
                all {
                    if (candidate.group == "org.sample" && candidate.module == "api" && candidate.version == "1.5") {
                        reject("version 1.5 is broken for 'org.sample:api'")
                    }
                }
            }
        }
    }
}

dependencies {
    "rejectConfig"("org.sample:api:1.+")
}
build.gradle
configurations {
    rejectConfig {
        resolutionStrategy {
            componentSelection {
                // Accept the highest version matching the requested version that isn't '1.5'
                all { ComponentSelection selection ->
                    if (selection.candidate.group == 'org.sample' && selection.candidate.module == 'api' && selection.candidate.version == '1.5') {
                        selection.reject("version 1.5 is broken for 'org.sample:api'")
                    }
                }
            }
        }
    }
}

dependencies {
    rejectConfig "org.sample:api:1.+"
}

请注意,版本选择从最高版本开始应用。所选版本将是所有组件选择规则都接受的第一个版本。如果没有任何规则明确拒绝某个版本,则该版本将被视为已接受。

同样,规则可以针对特定模块。模块必须以group:module的形式指定。

build.gradle.kts
configurations {
    create("targetConfig") {
        resolutionStrategy {
            componentSelection {
                withModule("org.sample:api") {
                    if (candidate.version == "1.5") {
                        reject("version 1.5 is broken for 'org.sample:api'")
                    }
                }
            }
        }
    }
}
build.gradle
configurations {
    targetConfig {
        resolutionStrategy {
            componentSelection {
                withModule("org.sample:api") { ComponentSelection selection ->
                    if (selection.candidate.version == "1.5") {
                        selection.reject("version 1.5 is broken for 'org.sample:api'")
                    }
                }
            }
        }
    }
}

组件选择规则在选择版本时也可以考虑组件元数据。可以考虑的可能的附加元数据是 ComponentMetadataIvyModuleDescriptor。请注意,此附加信息可能并不总是可用,因此应检查null值。

build.gradle.kts
configurations {
    create("metadataRulesConfig") {
        resolutionStrategy {
            componentSelection {
                // Reject any versions with a status of 'experimental'
                all {
                    if (candidate.group == "org.sample" && metadata?.status == "experimental") {
                        reject("don't use experimental candidates from 'org.sample'")
                    }
                }
                // Accept the highest version with either a "release" branch or a status of 'milestone'
                withModule("org.sample:api") {
                    if (getDescriptor(IvyModuleDescriptor::class)?.branch != "release" && metadata?.status != "milestone") {
                        reject("'org.sample:api' must have testing branch or milestone status")
                    }
                }
            }
        }
    }
}
build.gradle
configurations {
    metadataRulesConfig {
        resolutionStrategy {
            componentSelection {
                // Reject any versions with a status of 'experimental'
                all { ComponentSelection selection ->
                    if (selection.candidate.group == 'org.sample' && selection.metadata?.status == 'experimental') {
                        selection.reject("don't use experimental candidates from 'org.sample'")
                    }
                }
                // Accept the highest version with either a "release" branch or a status of 'milestone'
                withModule('org.sample:api') { ComponentSelection selection ->
                    if (selection.getDescriptor(IvyModuleDescriptor)?.branch != "release" && selection.metadata?.status != 'milestone') {
                        selection.reject("'org.sample:api' must be a release branch or have milestone status")
                    }
                }
            }
        }
    }
}

请注意,在声明组件选择规则时,始终需要 ComponentSelection 参数作为参数。