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

或者,即使对于同一版本,您请求的模块也可能随着时间的推移而更改,即所谓的更改版本。这种类型的更改模块的一个示例是 MavenSNAPSHOT模块,它始终指向最新发布的工件。换句话说,一个标准的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.+'
}

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

依赖管理动态依赖构建扫描
图 1. 构建扫描中的动态依赖关系

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

声明更改版本

团队可能会决定在发布新版本的应用程序或库之前实现一系列功能。允许消费者尽早集成其工件的未完成版本的常见策略是发布具有所谓的更改版本的模块。更改版本表明该功能集仍在积极开发中,尚未发布可供普遍使用的稳定版本。

在 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 会缓存更改版本的依赖项 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参数作为参数。