声明依赖关系

第 3 章让您初步了解如何告诉您的项目需要外部库才能正常运行。 DSL 配置块依赖项用于将一个或多个依赖项分配给配置。 外部依赖项并不是您可以为项目声明的唯一依赖项。 表 5.1 概述了各种类型的依赖关系。 在本书中,我们将讨论并应用其中的许多选项。 本章中解释了一些依赖类型,但其他依赖类型在另一章的上下文中会更有意义。 该表引用了每个用例。

Table 1. 表 5.1 Gradle 项目的依赖项类型
类型 描述 去哪里获取更多信息

外部模块依赖

对存储库中外部库的依赖,包括其提供的元数据

参考 5.4.2 章节

项目依赖

对另一个 Gradle 项目的依赖

参考 6.3.3 章节

文件依赖

对文件系统中一组文件的依赖

参考 5.4.3 章节

客户端模块依赖

对存储库中外部库的依赖,能够自行声明元数据

未涵盖 - 请参阅在线手册

Gradle运行时依赖

对 Gradle 的 API 或 Gradle 运行时附带的库的依赖

参考 8.5.7 章节

在本章中,我们将介绍外部模块依赖项和文件依赖项,但首先让我们看看 Gradle 的 API 中如何表示依赖项支持。

了解依赖 API 表示

每个 Gradle 项目都有一个依赖处理程序的实例,它由 DependencyHandler 接口表示。 您可以使用项目的 getter 方法 getDependency() 获取对依赖项处理程序的引用。 表 5.1 中提供的每种依赖项类型都是通过项目依赖项配置块中的依赖项处理程序的方法声明的。 每个依赖项都是 Dependency 类型的一个实例。 属性组、名称、版本和分类器清楚地标识了依赖关系。 图 5.6 说明了项目、依赖项处理程序和实际依赖项之间的关系。

image 2024 03 18 10 35 19 879
Figure 1. 图 5.6 可以在项目级别添加不同类型的依赖关系。

让我们首先看看如何声明外部模块依赖项、它们的表示法,以及如何配置它们以满足您的需求。

外部模块依赖

在 Gradle 的术语中,外部库(通常以 JAR 文件的形式)称为外部模块依赖项。 它们表示对项目层次结构之外的模块的依赖关系。 这种类型的依赖关系的特点是在存储库中可以清楚地识别它的属性。 在下一节中,我们将一一讨论每个属性。

依赖属性

当依赖项管理器查找存储库上的依赖项时,它通过属性组合来定位它。 至少,依赖项需要提供名称。 让我们借助第 5.1.2 节中检查过的 Hibernate 核心库来回顾一下依赖属性:

  • group:此属性通常标识组织、公司或项目。 该组可以使用点符号,但这不是强制性的。 对于 Hibernate 库,该组是 org.hibernate。

  • name:工件的名称唯一地描述了依赖关系。 Hibernate 的核心库的名称是 hibernate-core。

  • version:一个库可能有多个版本。 很多时候版本字符串由主要版本和次要版本组成。 您为 Hibernate 核心选择的版本是 3.6.3-Final。

  • classifier:有时工件会定义另一个属性,即分类器,用于区分具有相同组、名称和版本的工件,但需要进一步规范(例如,运行时环境)。 Hibernate 的核心库不提供分类器。

现在我们已经回顾了一些依赖属性,我们可以更仔细地了解 Gradle 希望如何在构建脚本中声明它们。

依赖符号表示

要声明项目中的依赖项,您可以使用以下语法:

dependencies {
    configurationName dependencyNotation1, dependencyNotation2, ...
}

您首先声明要分配依赖项的配置的名称,然后以您选择的表示法声明依赖项列表。 依赖符号有两种形式。 您可以提供属性名称及其值的映射,也可以提供快捷表示法作为用冒号分隔每个属性的字符串(见图 5.7)。 我们将在示例中查看这两种符号。

image 2024 03 18 10 41 32 432
Figure 2. 图 5.7 快捷表示法中的依赖属性

定义配置后,您可以轻松地使用它来分配相关的 Cargo 依赖项。 要在项目中使用 Cargo,您需要提供包含 Cargo API、核心容器实现和 Cargo Ant 任务的 JAR 文件。 值得庆幸的是,Cargo 提供了 UberJar,这是一个打包 API 和容器功能的单个 JAR 文件,这将使依赖关系管理变得更容易。 以下清单显示了如何将相关 Cargo 依赖项分配给 Cargo 配置。

ext.cargoGroup = 'org.codehaus.cargo'
ext.cargoVersion = '1.3.1'
dependencies {
    cargo group: cargoGroup, name: 'cargo-core-uberjar',
     version: cargoVersion
    cargo "$cargoGroup:cargo-ant:$cargoVersion"
}

如果您在项目中处理大量依赖项,那么将常用的依赖项属性分解为额外属性会很有帮助。 您可以在示例代码中通过创建和使用 Cargo 的依赖项组和版本属性的属性来执行此操作。

Gradle 不会为您选择默认存储库。 尝试在不配置存储库的情况下运行deployToLocalTomcat 任务将导致错误,如以下控制台输出所示:

$ gradle deployToLocalTomcat
:deployToLocalTomcat FAILED

FAILURE: Build failed with an exception.
* Where: Build file '/Users/benjamin/gradle-in-action/code/chapter5/cargo-configuration/buildxxx.gradle' line: 10
* What went wrong:
Execution failed for task ':deployToLocalTomcat'.
> Could not resolve all dependencies for configuration ':cargo'.
    > Could not find group:org.codehaus.cargo, module:cargo-core-uberjar, version:1.3.1.
    Required by:
      :cargo-configuration:unspecified
    > Could not find group:org.codehaus.cargo, module:cargo-ant,version:1.3.1.
    Required by:
      :cargo-configuration:unspecified

到目前为止,我们还没有讨论不同类型的存储库以及如何配置它们。 为了运行此示例,请添加以下存储库配置块:

repositories {
    mavenCentral()
}

无需完全理解此代码片段的复杂性。 重要的一点是您将项目配置为使用 Maven Central 下载 Cargo 依赖项。 在本章后面,您将学习如何配置其他存储库。

检查依赖性报告

当您运行 dependencies 帮助任务时,您现在可以看到打印了完整的依赖项树。 该树显示了您在构建脚本中声明的顶级依赖项及其传递依赖项:

$ gradle dependencies
:dependencies
------------------------------------------------------------
Root project
------------------------------------------------------------
cargo - Classpath for Cargo Ant tasks.
// 在构建脚本中声明顶级依赖项
+--- org.codehaus.cargo:cargo-core-uberjar:1.3.1
| +--- commons-discovery:commons-discovery:0.4
| | \--- commons-logging:commons-logging:1.0.4
| +--- jdom:jdom:1.0
| +--- dom4j:dom4j:1.4
// 指示请求的版本和选择的版本以解决库的版本冲突
| | +--- xml-apis:xml-apis:1.0.b2 -> 1.3.03
| | +--- jaxen:jaxen:1.0-FCS
| | +--- saxpath:saxpath:1.0-FCS
| | +--- msv:msv:20020414
| | +--- relaxngDatatype:relaxngDatatype:20020414
| | \--- isorelax:isorelax:20020414
| +--- jaxen:jaxen:1.0-FCS (*)
| +--- saxpath:saxpath:1.0-FCS (*)
| +--- msv:msv:20020414 (*)
| +--- relaxngDatatype:relaxngDatatype:20020414 (*)
| +--- isorelax:isorelax:20020414 (*)
| +--- com.sun.xml.bind:jaxb-impl:2.1.13
| | \--- javax.xml.bind:jaxb-api:2.1
| | +--- javax.xml.stream:stax-api:1.0-2
| | \--- javax.activation:activation:1.1
| +--- javax.xml.bind:jaxb-api:2.1 (*)
| +--- javax.xml.stream:stax-api:1.0-2 (*)
| +--- javax.activation:activation:1.1 (*)
| +--- org.apache.ant:ant:1.7.1
| | \--- org.apache.ant:ant-launcher:1.7.1
| +--- org.apache.ant:ant-launcher:1.7.1 (*)
| +--- xerces:xercesImpl:2.8.1
| | \--- xml-apis:xml-apis:1.3.03 (*)
| +--- xml-apis:xml-apis:1.3.03 (*)
| \--- commons-logging:commons-logging:1.0.4 (*)
// 在构建脚本中声明顶级依赖项
\--- org.codehaus.cargo:cargo-ant:1.3.1
\--- org.codehaus.cargo:cargo-core-uberjar:1.3.1 (*)
// 标记从依赖关系图中排除的传递依赖关系
(*) - dependencies omitted (listed previously)

如果仔细检查依赖关系树,您会发现标有星号的依赖关系已被省略。 这意味着依赖项管理器选择了该库的相同版本或另一个版本,因为它被声明为另一个顶级依赖项的传递依赖项。 有趣的是,UberJar 就是这种情况,因此您甚至不必在构建脚本中声明它。 Ant 任务库将自动确保拉入该库。Gradle 对于版本冲突的默认解决策略是最新的优先,也就是说,如果依赖关系图包含同一库的两个版本,它会自动选择最新的。 对于 xml-apis 库,Gradle 选择版本 1.3.03 而不是 1.0.b2,这由箭头 (->) 指示。 正如您所看到的,分析依赖关系报告公开的信息非常有帮助。 当您想要找出哪个顶级依赖项声明了特定的传递依赖项以及为什么选择或省略了库的特定版本时,依赖项报告是一个不错的起点。 接下来,我们将了解如何排除传递依赖。

排除传递依赖

在处理像 Maven Central 这样的公共存储库时,您可能会遇到维护不善的依赖项元数据。 Gradle 使您可以完全控制传递依赖项,因此您可以决定完全排除所有传递依赖项或有选择地排除特定依赖项。 假设您明确想要指定库 xml-apis 的不同版本,而不是使用 Cargo 的 UberJar 提供的传递依赖项。 在实践中,当您自己的某些功能构建在特定版本的 API 或框架之上时,通常会出现这种情况。 下一个清单显示了如何使用 ModuleDependency 中的排除方法来排除传递依赖项。

清单 5.4 排除单个依赖项
dependencies {
    cargo('org.codehaus.cargo:cargo-ant:1.3.1') {
        // 可以用快捷方式或映射符号来声明排除
        exclude group: 'xml-apis', module: 'xml-apis'
    }
    cargo 'xml-apis:xml-apis:2.0.2'
}

请注意,排除属性与常规依赖符号略有不同。 您可以使用属性 group 和 / 或 module。 Gradle 不允许您仅排除依赖项的特定版本,因此版本属性不可用。

有时,依赖项的元数据声明存储库中不存在的传递依赖项。 结果,您的构建将会失败。 这只是您想要完全控制传递依赖项的情况之一。 Gradle 允许您使用 transitive 属性排除所有传递依赖项,如以下清单所示。

清单 5.5 排除所有传递依赖
dependencies {
    cargo('org.codehaus.cargo:cargo-ant:1.3.1') {
        transitive = false
    }
    // Selectively declare required dependencies
}

到目前为止,您仅声明了对外部库的特定版本的依赖关系。 让我们看看如何解析依赖项的最新版本或版本范围内的最新版本。

动态版本声明

动态版本声明具有特定的语法。 如果您想使用最新版本的依赖项,则必须使用占位符 latest.integration。 例如,要声明 Cargo Ant 任务的最新版本,您可以使用 org.codehaus .cargo:cargo-ant:latest-integration。 或者,您可以通过用加号 (+) 分隔来声明版本属性中您想要动态的部分。 以下清单显示了如何解析最新的 1.x 版本的 Cargo Ant 库。

清单 5.6 声明对最新 Cargo 1.x 版本的依赖
dependencies {
    cargo 'org.codehaus.cargo:cargo-ant:1.+'
}

Gradle 的依赖项帮助任务清楚地表明选择了哪个版本:

$ gradle –q dependencies
------------------------------------------------------------
Root project
------------------------------------------------------------
cargo - Classpath for Cargo Ant tasks.
\--- org.codehaus.cargo:cargo-ant:1.+ -> 1.3.1
\--- ...

另一种选择是为依赖项选择一系列版本中的最新版本。 要了解有关语法的更多信息,请随时查看 Gradle 的在线手册。

我什么时候应该使用动态版本?

简短的回答是很少甚至从不。 可靠且可重复的构建至关重要。 选择最新版本的库可能会导致构建失败。 更糟糕的是,您可能会在不知情的情况下引入不兼容的库版本和副作用,这些副作用很难发现并且仅在应用程序运行时发生。 因此,声明库的确切版本应该是常态。

文件依赖

如前所述,不使用自动依赖项管理的项目将其外部库组织为源代码的一部分或本地文件系统中。 尤其是在将项目迁移到 Gradle 时,您不想立即更改构建的每个方面。 Gradle 使您可以轻松配置文件依赖项。 您将通过引用本地文件系统中的 Cargo 库来为您的项目模拟这一点。 以下清单显示了一个任务,该任务将从 Maven Central 解析的依赖项复制到用户主目录下的子目录 libs/cargo 中。

// Gradle API提供的语法糖; 与调用configurations.getByName-('cargo').asFileTree相同。
task copyDependenciesToLocalDir(type: Copy) {
    from configurations.cargo.asFileTree
    into "${System.properties['user.home']}/libs/cargo"
}
清单 5.8 声明文件依赖关系
dependencies {
    cargo fileTree(dir: "${System.properties['user.home']}/libs/cargo", include: '*.jar')
}

因为您处理的存储库不需要您使用特定模式声明依赖项,所以您也不需要定义存储库配置块。 接下来,我们将重点介绍 Gradle 支持的各种存储库类型以及它们的配置方式。