在 Gradle 中,依赖项与特定的范围关联,例如编译时或运行时。这些范围由配置表示,每个配置都通过唯一的名称标识。

dependency management configurations

Gradle 插件通常会向你的项目添加预定义的配置

例如,当应用 Java 插件时,它会向你的项目添加用于源代码编译 (implementation)、测试执行 (testImplementation) 等(apicompileOnlyruntimeOnly 等)的配置。

build.gradle.kts
plugins {
    `java-library`
}
dependencies {
    implementation("org.hibernate:hibernate-core:3.6.7.Final")
    testImplementation("junit:junit:4.+")
    api("com.google.guava:guava:23.0")
}
build.gradle
plugins {
    id 'java-library'
}
dependencies {
    implementation 'org.hibernate:hibernate-core:3.6.7.Final'
    testImplementation 'junit:junit:4.+'
    api 'com.google.guava:guava:23.0'
}

本示例重点介绍了在 Java 项目中声明在 implementationtestImplementationapi 配置上的依赖项。有关详细信息,请参阅Java 插件文档

可解析配置和可消费配置

配置不仅用于声明依赖项,它们在依赖管理中扮演着多种角色

  1. 依赖声明角色:定义一组依赖项的配置。

  2. 消费者角色:用于将依赖项解析为 artifact 的配置。

  3. 生产者角色:暴露 artifact 以供其他项目消费的配置。

1. 用于声明依赖项的配置(即,可声明配置)

要在你的项目中声明依赖项,你可以使用或创建可声明配置。这些配置有助于组织和分类项目不同部分的依赖项。

例如,要表示对另一个项目的依赖,你会使用像 implementation 这样的可声明配置

build.gradle.kts
dependencies {
    // add a project dependency to the implementation configuration
    implementation(project(":lib"))
}
build.gradle
dependencies {
    // add a project dependency to the implementation configuration
    implementation project(":lib")
}

用于声明依赖项的配置定义并管理你的代码在编译或测试等任务中所需的特定库或项目。

2. 用于消费者的配置(即,可解析配置)

要控制依赖项如何在你的项目中解析和使用,你可以使用或创建可解析配置。这些配置定义了你的项目在不同阶段(如编译或运行时)所需的类路径和其他 artifact 集。

例如,implementation 配置声明依赖项,而 compileClasspathruntimeClasspath 是为特定目的设计的可解析配置。解析后,它们分别代表编译和运行时所需的类路径。

build.gradle.kts
configurations {
    // declare a resolvable configuration that is going to resolve the compile classpath of the application
    resolvable("compileClasspath") {
        //isCanBeConsumed = false
        //isCanBeDeclared = false
        extendsFrom(implementation)
    }
}
build.gradle
configurations {
    // declare a resolvable configuration that is going to resolve the compile classpath of the application
    resolvable("compileClasspath") {
        //canBeConsumed = false
        //canBeDeclared = false
        extendsFrom(implementation)
    }
}

可解析配置是可以被解析以生成一组文件或 artifact 的配置。这些配置用于定义构建过程不同阶段(如编译或运行时)的类路径。

3. 用于生产者的配置(即,可消费配置)

可消费配置用于向其他项目暴露 artifact。这些配置定义了你的项目哪些部分可以被其他项目消费,如 API 或运行时依赖项,但不旨在直接在你的项目内部进行解析。

例如,exposedApi 配置是一个可消费配置,它向消费者暴露组件的 API。

build.gradle.kts
configurations {
    // a consumable configuration meant for consumers that need the API of this component
    consumable("exposedApi") {
        //isCanBeResolved = false
        //isCanBeDeclared = false
        extendsFrom(implementation)
    }
}
build.gradle
configurations {
    // a consumable configuration meant for consumers that need the API of this component
    consumable("exposedApi") {
        //canBeResolved = false
        //canBeDeclared = false
        extendsFrom(implementation)
    }
}

库通常提供可消费配置,如 apiElements(用于编译)和 runtimeElements(用于运行时依赖项)。这些配置暴露了其他项目消费所需的 artifact,而无需在当前项目内部解析。canBeDeclaredisCanBeConsumedisCanBeResolved 标志有助于区分这些配置的角色。

配置标志和角色

配置有三个关键标志

  • canBeResolved: 指示此配置旨在将一组依赖项解析为依赖图。可解析配置不应是可声明或可消费的。

  • canBeConsumed: 指示此配置旨在向项目外部暴露 artifact。可消费配置不应是可声明或可解析的。

  • canBeDeclared: 指示此配置旨在声明依赖项。可声明配置不应是可解析或可消费的。

配置只能启用这些标志中的一个。

简而言之,配置的角色由 canBeResolvedcanBeConsumedcanBeDeclared 标志决定

配置角色 可解析 可消费 可声明

依赖范围

false

false

true

为特定用途进行解析

true

false

false

暴露给消费者

false

true

false

遗留,不使用

true

true

true

为了向后兼容,这些标志的默认值为 true,但作为插件作者,你应该始终确定这些标志的正确值,否则可能会意外引入解析错误。

本示例演示了如何在 Gradle 中手动声明核心 Java 配置(通常由Java 插件提供)。

build.gradle.kts
// declare a "configuration" named "implementation"
val implementation by configurations.creating {
    isCanBeConsumed = false
    isCanBeResolved = false
}

dependencies {
    // add a project dependency to the implementation configuration
    implementation(project(":lib"))
}

configurations {
    // declare a resolvable configuration that is going to resolve the compile classpath of the application
    resolvable("compileClasspath") {
        //isCanBeConsumed = false
        //isCanBeDeclared = false
        extendsFrom(implementation)
    }
    // declare a resolvable configuration that is going to resolve the runtime classpath of the application
    resolvable("runtimeClasspath") {
        //isCanBeConsumed = false
        //isCanBeDeclared = false
        extendsFrom(implementation)
    }
}

configurations {
    // a consumable configuration meant for consumers that need the API of this component
    consumable("exposedApi") {
        //isCanBeResolved = false
        //isCanBeDeclared = false
        extendsFrom(implementation)
    }
    // a consumable configuration meant for consumers that need the implementation of this component
    consumable("exposedRuntime") {
        //isCanBeResolved = false
        //isCanBeDeclared = false
        extendsFrom(implementation)
    }
}
build.gradle
// declare a "configuration" named "implementation"
configurations {
    // declare a "configuration" named "implementation"
    implementation {
        canBeConsumed = false
        canBeResolved = false
    }
}

dependencies {
    // add a project dependency to the implementation configuration
    implementation project(":lib")
}

configurations {
    // declare a resolvable configuration that is going to resolve the compile classpath of the application
    resolvable("compileClasspath") {
        //canBeConsumed = false
        //canBeDeclared = false
        extendsFrom(implementation)
    }
    // declare a resolvable configuration that is going to resolve the runtime classpath of the application
    resolvable("runtimeClasspath") {
        //canBeConsumed = false
        //canBeDeclared = false
        extendsFrom(implementation)
    }
}

configurations {
    // a consumable configuration meant for consumers that need the API of this component
    consumable("exposedApi") {
        //canBeResolved = false
        //canBeDeclared = false
        extendsFrom(implementation)
    }
    // a consumable configuration meant for consumers that need the implementation of this component
    consumable("exposedRuntime") {
        //canBeResolved = false
        //canBeDeclared = false
        extendsFrom(implementation)
    }
}

创建了以下配置

  • implementation: 用于声明项目依赖项,但既不可消费也不可解析。

  • compileClasspath + runtimeClasspath: 可解析配置,从 implementation 中收集编译时和运行时依赖项。

  • exposedApi + exposedRuntime: 可消费配置,将 artifact(API 和运行时)暴露给其他项目,但不用于内部解析。

此设置模拟了Java 插件implementationcompileClasspathruntimeClasspathapiElementsruntimeElements 配置的行为。

已废弃的配置

过去,一些配置没有定义它们旨在用于的角色。

当配置以非预期的方式使用时,会发出废弃警告。要修复废弃,你需要停止以废弃的角色使用该配置。所需的具体更改取决于配置的使用方式以及是否存在应替代使用的其他配置。

创建自定义配置

你可以定义自定义配置来声明用于特定目的的独立依赖范围。

假设你想生成包含嵌入在 Java 源代码注释中的 AsciiDoc 格式的 Javadoc。通过设置 asciidoclet 配置,你可以让 Gradle 使用 Asciidoclet,从而使你的 Javadoc 任务能够生成具有增强格式选项的 HTML 文档。

build.gradle.kts
val asciidoclet by configurations.creating

dependencies {
    asciidoclet("org.asciidoctor:asciidoclet:1.+")
}

tasks.register("configureJavadoc") {
    doLast {
        tasks.javadoc {
            options.doclet = "org.asciidoctor.Asciidoclet"
            options.docletpath = asciidoclet.files.toList()
        }
    }
}
build.gradle
configurations {
    asciidoclet
}

dependencies {
    asciidoclet 'org.asciidoctor:asciidoclet:1.+'
}

你可以使用 configurations 块管理自定义配置。配置必须有名称,并且可以相互扩展。有关详细信息,请参阅 ConfigurationContainer API。

配置旨在用于单一角色:声明依赖项、执行解析或定义可消费变体。

创建自定义配置有三个主要用例

  1. API/实现分离:创建自定义配置以将 API 依赖项(暴露给消费者)与实现依赖项(在编译或运行时内部使用)分开。

    • 你可以创建一个 api 配置用于消费者将依赖的库,以及一个 implementation 配置用于仅在内部需要的库。api 配置通常被下游项目消费,而 implementation 依赖项对消费者隐藏但在内部使用。

    • 这种分离确保你的项目在公共 API 和严格的内部机制之间保持清晰的界限。

  2. 可解析配置创建:创建自定义可解析配置以在不同构建阶段解析特定的依赖项集,例如类路径。

    • 你可以创建一个 compileClasspath 配置,该配置仅解析编译项目所需的依赖项。类似地,你可以创建一个 runtimeClasspath 配置来解析在运行时运行项目所需的依赖项。

    • 这允许精细控制哪些依赖项在不同的构建阶段可用,例如编译或测试。

  3. 从依赖配置创建可消费配置:创建自定义可消费配置,以暴露 artifact 或依赖项供其他项目消费,这通常发生在你的项目生成 JAR 等 artifact 时。

    • 你可以创建一个 exposedApi 配置,以暴露项目的 API 依赖项供其他项目消费。类似地,可以创建一个 runtimeElements 配置,以暴露其他项目所需的运行时依赖项或 artifact。

    • 可消费配置确保只有必要的 artifact 或依赖项暴露给消费者。

Configuration API 孵化中方法

ConfigurationContainer API 中的几个孵化中工厂方法——resolvable()consumable()dependencyScope()——可用于简化具有特定角色的配置创建。

这些方法帮助构建作者记录配置的用途,并避免手动设置各种配置标志,从而简化流程并确保一致性

  • resolvable(): 创建一个旨在解析依赖项的配置。这意味着该配置可用于解析依赖项,但不能被其他项目消费。

  • consumable(): 创建一个旨在供其他项目消费,但不用于自身解析依赖项的配置。

  • dependencyScope(): 创建一个建立依赖范围的配置,根据用例设置必要的属性,使其既可以作为消费者,也可以作为提供者。

配置继承

配置可以继承其他配置,创建继承层次结构。

配置使用 Configuration.extendsFrom(Configuration…​) 方法形成继承层次结构。配置可以扩展除了分离配置之外的任何其他配置,无论其在构建脚本或插件中如何定义。

避免用不可消费或不可解析的配置分别扩展可消费或可解析的配置。

例如,在一个已经使用 JUnit 进行测试的项目中,你可以定义一个名为 smokeTest 的专用配置来运行冒烟测试。每个冒烟测试执行一个 HTTP 请求来验证 Web 服务端点。为了重用现有的测试框架依赖项,smokeTest 配置应扩展自 testImplementation。这使得冒烟测试可以利用与单元测试相同的依赖项,而无需重复。该配置可以在 build.gradle(.kts) 中声明如下

build.gradle.kts
val smokeTest by configurations.creating {
    extendsFrom(configurations.testImplementation.get())
}

dependencies {
    testImplementation("junit:junit:4.13")
    smokeTest("org.apache.httpcomponents:httpclient:4.5.5")
}
build.gradle
configurations {
    smokeTest.extendsFrom testImplementation
}

dependencies {
    testImplementation 'junit:junit:4.13'
    smokeTest 'org.apache.httpcomponents:httpclient:4.5.5'
}

此设置使 smokeTest 源集能够继承 JUnit 和任何其他测试依赖项,从而更容易定义和执行冒烟测试,同时将其与单元测试分开。

配置只能扩展同一项目中的配置。

扩展配置时,新配置会继承

  • 依赖项

  • 依赖约束

  • 排除规则

  • artifact

  • 能力

扩展包括属性。它也扩展可消费/可解析/可声明状态。

依赖解析

所有依赖解析 API 的入口点是一个可解析配置。Java 插件主要使用 compileClasspathruntimeClasspath 配置分别解析用于编译和运行时的 jar 包。

可解析配置旨在启动依赖解析。待解析的依赖项声明在依赖范围配置上。Java 插件使用 apiimplementationruntimeOnly 等依赖范围配置作为可解析配置解析的依赖项来源。

考虑以下示例,它演示了如何声明一组旨在用于解析的配置

此示例使用孵化中的 API。
build.gradle.kts
val implementation = configurations.dependencyScope("implementation")
val runtimeClasspath = configurations.resolvable("runtimeClasspath") {
    extendsFrom(implementation.get())
}
build.gradle
configurations {
    dependencyScope("implementation")
    resolvable("runtimeClasspath") {
        extendsFrom(implementation)
    }
}

可以在 implementation 配置上使用 dependencies 块声明依赖项。有关可声明的依赖项类型以及自定义依赖项声明的各种选项的更多信息,请参阅声明依赖项章节。

build.gradle.kts
dependencies {
    implementation("com.google.guava:guava:33.2.1-jre")
}
build.gradle
dependencies {
    implementation("com.google.guava:guava:33.2.1-jre")
}

现在我们已经创建了一个用于声明依赖项的依赖范围配置,以及一个用于解析这些依赖项的可解析配置,我们可以使用 Gradle 的依赖解析 API 来访问解析结果。

不安全的配置解析错误

解析配置可能会对 Gradle 的项目模型产生副作用。因此,Gradle 必须管理对每个项目配置的访问。

配置可能以多种不安全的方式进行解析。例如

  • 一个项目的任务在其操作中直接解析另一个项目的配置。

  • 任务将另一个项目的配置指定为输入文件集。

  • 一个项目的构建脚本在评估期间解析另一个项目的配置。

  • 项目配置在 settings 文件中解析。

Gradle 为每次不安全访问生成废弃警告。

不安全访问可能导致不确定的错误。你应该在构建中修复不安全访问警告

在大多数情况下,你可以通过创建适当的跨项目依赖来解决不安全访问问题。