在 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. 消费者角色:用于将依赖项解析为工件的配置。

  3. 生产者角色:公开工件供其他项目消费的配置。

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. 消费者配置(即,可解析的配置)

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

例如,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)
    }
}

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

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

可消费配置用于将工件暴露给其他项目。这些配置定义了项目的哪些部分可以被其他项目消费,例如 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(用于运行时依赖项)。这些配置公开了其他项目消费所需的工件,而无需在当前项目中解析。canBeDeclaredisCanBeConsumedisCanBeResolved 标志有助于区分这些配置的角色。

配置标志和角色

配置有三个关键标志

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

  • canBeConsumed 表示此配置旨在将工件暴露到项目外部。可消费配置不应是可声明或可解析的。

  • 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:可消费配置,将工件(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. 从依赖配置创建可消费配置:创建自定义可消费配置以将工件或依赖项暴露给其他项目消费,通常在您的项目生成 JAR 等工件时使用。

    • 您可以创建一个 exposedApi 配置,以将您项目的 API 依赖项暴露给其他项目消费。同样,可以创建一个 runtimeElements 配置,以暴露其他项目所需的运行时依赖项或工件。

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

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 和任何其他测试依赖项,从而更容易定义和执行冒烟测试,同时将其与单元测试分开。

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

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

  • 依赖项

  • 依赖约束

  • 排除规则

  • 工件

  • 能力

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

依赖解析

所有依赖解析 API 的入口点是可解析的Configuration。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 必须管理对每个项目配置的访问。

配置可能会以多种方式不安全地解析。例如

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

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

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

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

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

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

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