读书笔记《gradle-effective-implementations-guide-second-edition》依赖关系管理
Java 没有真正支持将版本化库作为依赖项使用。例如,我们无法用 Java 表示我们的类是依赖于 lib-1.0.jar
还是lib-2.0.jar
。有一些开源解决方案可以处理依赖关系,并允许我们表达我们的 Java 代码是依赖于 lib-1.0.jar
还是 lib -2.0.jar
。最受欢迎的是 Maven 和 Apache Ivy。 Maven 是一个完整的构建工具,并具有依赖管理机制。 Ivy 只涉及依赖管理。
这两种工具都支持版本库与有关这些库的元数据一起存储的存储库。一个库可以依赖于其他库,并在该库的元数据中进行描述。元数据在描述符 XML 文件中描述。 Ivy 完全支持 Maven 描述符文件和存储库;它还增加了一些额外的功能。因此,使用 Ivy,您将获得与 Maven 相同的功能,然后获得更多功能。这就是 Gradle 在底层使用 Ivy API 来执行依赖管理的原因。 Gradle 还在 Ivy 之上添加了一些额外的糖,因此我们可以以非常灵活的方式定义和使用依赖项。
在 Gradle 构建文件中,我们将依赖项组合在一个配置中。配置具有名称,并且配置可以相互扩展。通过配置,我们可以创建逻辑依赖组。例如,我们可以创建一个 javaCompile
配置来包含编译 Java 代码所需的依赖项。我们可以根据需要向构建中添加任意数量的配置。我们没有直接在配置中定义我们的依赖关系。当我们定义依赖时,可以使用配置,就像标签一样。
每个 Gradle 构建都有一个 ConfigurationContainer
对象。此对象可通过名称为 containers
的 Project
属性访问。我们可以使用闭包来配置带有 Configuration
对象的容器。每个 Configuration
对象至少有一个名称,但我们可以更改更多属性。如果配置与依赖项有版本冲突,我们可以设置解决策略,或者我们可以更改配置的可见性,使其在项目之外不可见。 Configuration
对象也有一个层次结构。所以我们可以从现有的 Configuration
对象扩展继承设置。
在下面的示例中,我们将创建一个名为 commonsLib
的新配置来保存我们的依赖项和一个扩展的mainLib
配置 ;commonsLib
。扩展的 mainLib
配置从 commonsLib
获取所有设置和依赖,我们也可以分配额外的依赖:
构建的输出显示了配置的名称,如下所示:
许多插件向 ConfigurationContainer
添加了新的配置。我们在上一章中使用了 Java 插件,它为我们的项目添加了四个配置。使用内置 dependencies
任务,我们可以大致了解项目的已定义依赖项和配置。
以下构建脚本使用 Java 插件:
如果我们执行 dependencies
任务,我们会得到以下输出:
注意我们的项目中已经有六个配置。下表显示了配置和使用该配置的任务:
配置 |
扩展 |
由任务使用 |
说明 |
|
- |
|
这些是编译时编译源文件所需的依赖项 |
|
|
- |
这些是应用程序运行时的依赖项,但编译不需要 |
|
|
|
这些是编译测试源文件的依赖项 |
|
|
|
这些是运行测试所需的所有依赖项 |
|
- |
|
这包含工件,例如项目创建的 JAR 文件 |
|
|
- |
这是包含所有运行时依赖项的默认配置 |
如果我们的代码对库有依赖,我们可以使用 compile
配置来设置依赖。然后,依赖项在 runtime
、 testCompile
、 testRuntime
和 默认
配置。
依赖项通常存储在某种存储库中。存储库具有定义版本库模块路径模式的布局。例如,Gradle 知道 Maven 存储库的布局。 Ivy 存储库可以有自定义布局,使用 Gradle,我们可以配置自定义布局。可以通过文件系统、HTTP、SSH 或其他协议访问存储库。
我们可以在 Gradle 构建文件中声明几种存储库类型。 Gradle 提供了一些预配置的存储库,但使用自定义 Maven 或 Ivy 存储库也非常容易。我们还可以声明一个简单的文件系统存储库,用于解析和查找依赖项。下表显示了我们可以使用的预配置和自定义存储库:
存储库类型 |
说明 |
Maven 仓库 |
这是远程计算机或文件系统上的 Maven 布局存储库。 |
Bintray JCenter 存储库 |
这是预配置的 Maven 布局存储库,用于在 Bintray JCenter 存储库中搜索依赖项。这是 Maven 中央存储库的超集。 |
Maven 中央存储库 |
这是预配置的 Maven 布局存储库,用于在 Maven 中央存储库中搜索依赖项。 |
Maven本地仓库 |
这是在本地 Maven 存储库中查找依赖项的预配置 Maven 存储库。 |
常春藤仓库 |
这是可以位于本地或远程计算机上的 Ivy 存储库。 |
平面目录存储库 |
这是计算机本地文件系统或网络共享上的简单存储库。 |
我们使用 repositories()
方法定义一个存储库。此方法接受用于配置 org.gradle.api.artifacts.dsl.RepositoryHandler
对象的闭包。
许多 Java 项目使用 Maven 作为构建工具,并使用它的依赖项管理功能。 Maven 存储库存储库,其中包含在描述符 XML 文件中描述的版本信息和元数据。 Maven 存储库的布局是固定的,并遵循 someroot/[organization]/[module]/[revision]/ [module]-[revision].[ext]
模式。 organization
部分根据组织名称中使用的点拆分为子文件夹。例如,如果组织名称是 org.gradle
,则 org
文件夹与 gradle
子文件夹需要在 Maven 存储库中。一个带有组织名称的 JAR 库, org.gradle
;模块名称, gradle-api
;和修订版, 1.0
,通过 someroot/org/gradle/gradle-api/1.0/gradle-api-1.0解决。 jar
路径。
Bintray JCenter 是一个相对较新的公共 Maven 存储库,其中存储了很多 Maven 开源依赖项。它是 Maven 中央存储库的超集,还包含直接在 JCenter 发布的依赖项工件。此外,将我们自己的工件部署到 Bintray JCenter 非常容易。访问存储库的 URL 是 https://jcenter.bintray.com。 Gradle 为 JCenter 提供了一个快捷方式,这样我们就不必在 repositories
配置块中自己输入 URL。快捷方式是 jcenter()
。
Maven 中央存储库位于 https://repo1.maven.org/maven2并包含很多库。许多开源项目将其工件部署到 Maven 的中央存储库。我们可以在 repositories()
方法的配置闭包中使用 mavenCentral()
方法。以下示例是一个构建文件,我们在其中定义了 Maven 中央存储库和 Bintray JCenter:
如果您以前在计算机上使用过 Maven,那么您很有可能拥有一个本地 Maven 存储库。 Maven 将使用我们主目录中的隐藏文件夹来存储下载的依赖库。我们可以使用 mavenLocal()
方法将这个本地 Maven 存储库添加到存储库列表中。我们可以将 Maven 本地存储库添加到我们的构建文件中,如下所示:
Bintray JCenter 和 Maven 中央和本地存储库是预配置的 Maven 存储库。我们还可以添加一个遵循 Maven 布局的自定义存储库。例如,我们公司可以通过 Intranet 提供一个 Maven 存储库。我们使用 maven()
或mavenRepo()
方法定义 Maven 存储库的 URL。
示例构建文件使用这两种方法添加两个新的 Maven 存储库,可通过我们的内网获得,如下所示:
这两种方法都通过闭包和方法参数的组合来配置存储库。有时我们必须访问将元数据存储在描述符 XML 文件中的 Maven 存储库,但实际的 JAR 文件位于不同的位置。为了支持这种情况,我们必须设置 artifactUrls
属性并分配存储 JAR 文件的服务器的地址:
要使用基本身份验证访问 Maven 存储库,我们可以在定义存储库时设置凭据,如下所示:
将 username
和 password
作为纯文本存储在构建文件中并不是一个好主意,因为任何人都可以读取我们的密码,如果以纯文本形式存储。如果我们在 Gradle 用户主目录的gradle.properties
文件中定义属性,对属性文件应用正确的安全约束,并在我们的构建文件。
常春藤存储库具有可自定义的布局,这意味着没有像 Maven 存储库那样的单一预定义布局。 Ivy 存储库的默认布局具有 someroot/[organization]/[module]/[revision]/[type]s/[artifact].[ext]
模式。与 Maven 布局一样,组织的名称不会拆分为子文件夹。因此,我们的 gradle
模块具有 org.gradle
组织名称和 gradle- api
带有修订版的工件 1.0
通过 someroot/org.gradle/gradle/1.0/jars/gradle-api 解决.jar
路径。
我们使用相同的 repositories()
方法来配置 Ivy 存储库。我们使用 ivy()
方法来配置 Ivy 存储库的设置。我们定义存储库的 URL,以及可选的名称,如下所示:
如果我们的 Ivy 存储库有 Maven 布局,我们可以将 layout
属性设置为 maven
。我们可以使用相同的属性为存储库定义自定义布局。我们将定义用于解析描述符 XML 文件和实际库文件的模式。
下表显示了我们可以使用的不同布局名称以及预配置布局的默认模式:
布局名称 |
模式 Ivy 描述符 |
模式工件 |
|
|
|
|
|
|
|
风俗 |
定制 |
示例构建文件使用预配置的布局名称 gradle
和 maven
以及自定义模式,如下所示:
除了使用 layout()
方法来定义自定义模式,我们可以使用 ivyPattern()
和 artifactPattern()
定义 Ivy 存储库模式的方法:
要访问使用基本身份验证保护的 Ivy 存储库,我们必须传递我们的凭据。就像安全的 Maven 存储库一样,最好将用户名和密码作为属性存储在 $USER_HOME/.gradle/gradle.properties
文件中:
要使用本地文件系统上的简单存储库或映射为本地存储的网络共享,我们必须使用 flatDir()
方法。 flatDir()
方法接受参数或闭包来配置正确的目录。我们可以分配单个目录或多个目录。
Gradle 将使用它找到的第一个匹配来解析配置目录中的文件,并使用以下模式:
[artifact]-[version].[ext]
[artifact]-[version]-[classifier].[ext]
[工件].[ext]
[artifact]-[classifier].[ext]
以下示例构建文件定义了一个平面目录存储库:
我们讨论了如何使用依赖配置将依赖组合在一起;我们还看到了必须如何定义存储库以便解决依赖关系,但我们还没有讨论如何定义实际的依赖关系。我们在构建项目中使用 dependencies{}
脚本块定义依赖项。我们定义了一个闭包以传递给 dependencies{}
脚本块,其中包含依赖项的配置。
我们可以定义不同类型的依赖。下表显示了我们可以使用的类型:
依赖类型 |
方法 |
说明 |
外部模块依赖 |
- |
这是对存储库中外部模块或库的依赖。 |
项目依赖 |
|
这是对另一个 Gradle 项目的依赖。 |
文件依赖 |
|
这是对本地计算机上文件集合的依赖。 |
客户端模块依赖 |
|
这是对外部模块的依赖,其中工件存储在存储库中,但有关模块的元信息在构建文件中。我们可以使用这种类型的依赖覆盖元信息。 |
Gradle API 依赖 |
|
这是对当前 Gradle 版本的 Gradle API 的依赖。我们在开发 Gradle 插件和任务时使用此依赖项。 |
本地 Groovy 依赖项 |
|
这是对当前 Gradle 版本使用的 Groovy 库的依赖。我们在开发 Gradle 插件和任务时使用此依赖项。 |
最常用的依赖是外部模块依赖。我们可以用不同的方式定义一个模块依赖。例如,我们可以使用参数来设置组名、模块名和依赖的修订版。我们还可以使用 String
表示法在单个字符串中设置组名、模块名和修订版。我们总是将依赖项分配给特定的依赖项配置。依赖配置必须由我们自己定义或者我们已经应用到我们的项目的插件。
在下面的示例构建文件中,我们将使用 Java 插件,以便获得 compile
和runtime
依赖配置。我们还将使用不同的语法规则为每个配置分配几个外部模块依赖项:
请记住,Gradle 构建文件是一个 Groovy 脚本文件,因此我们可以定义变量来设置值并在 dependencies{}
脚本块配置闭包中使用它们。如果我们重写之前的构建文件,我们会得到以下输出:
Gradle 将在 JCenter 存储库中查找描述符文件。如果找到该文件,则下载模块的工件和模块的依赖项,并使其可用于依赖项配置。
要查看依赖项和传递依赖项,我们调用内置的 dependencies
任务。我们得到以下输出:
要仅下载外部依赖项的工件而不下载传递依赖项,我们可以将依赖项的 transitive
属性设置为 false
。我们可以使用闭包或作为参数列表中的额外属性来设置属性,如下所示:
我们还可以使用 exclude()
方法排除一些传递依赖。 Gradle 将查看模块的描述符文件并排除我们使用 exclude()
方法添加的任何依赖项。
例如,在下面的构建文件中,我们排除了 org.slf4j:sl4j-api
传递依赖:
要获得仅具有外部模块依赖项的工件,我们可以使用 artifact-only 表示法。当存储库没有模块描述符文件并且我们想要获取工件时,我们也必须使用此表示法。我们必须在工件的扩展之前添加一个 @
符号。当我们使用这种表示法时,Gradle 不会查看模块描述符文件(如果可用):
我们甚至可以在完整配置上设置传递行为。每个配置都有一个 transitive
属性。为了改变我们在配置。在以下示例构建文件中,我们在 runtime
配置上设置了 transitive
属性:
在 Maven 存储库中,我们可以对依赖项使用分类器。例如,模块描述符文件为库的不同 JDK 版本定义了 jdk16
和jdk15
分类器。我们可以在 Gradle 依赖定义中使用 classifier
来选择具有给定分类器的依赖,如下所示:
一个 Maven 仓库中一个模块的模块描述符只能有一个工件;但是在 Ivy 存储库中,我们可以为单个模块定义多个工件。每组工件都在一个配置中组合在一起。默认配置包含属于该模块的所有工件。如果我们在为 Ivy 模块定义依赖项时没有指定配置属性,则使用 default
配置。如果我们想使用属于这个特定配置的工件,我们必须指定 configuration
属性,如下所示:
Gradle 项目可以相互依赖。为了定义这样的依赖,我们使用 project()
方法并使用另一个项目的名称作为参数。 Gradle 会在这个项目中寻找一个默认的依赖配置,并使用这个依赖配置。我们可以使用 configuration
属性为每个项目使用不同的依赖配置作为依赖:
我们可以使用 FileCollection
添加依赖项。我们可以使用 files()
和 fileTree()
方法将依赖项添加到配置中。必须将依赖关系解析为实际的工件。
以下示例使用文件依赖项进行编译配置:
通常,Gradle 将使用在存储库中找到的依赖项的描述符 XML 文件,以查看需要下载哪些工件和可选的传递依赖项。但是,这些描述符文件可能配置错误,因此,我们可能希望自己覆盖描述符以确保依赖关系正确。为此,我们必须使用 module()
方法来定义依赖项的传递依赖项。然后 Gradle 将使用我们的配置,而不是存储库中模块提供的配置,如下所示:
当我们开发 Grails 插件和任务时,我们可以定义对当前 Gradle 版本使用的 Gradle API 和 Groovy 库的依赖。我们可以使用 gradleApi()
和 localGroovy()
方法来做到这一点。
以下示例定义了项目的编译依赖配置中的依赖:
我们可以通过 Project
对象的 configurations
属性访问构建文件或任务中依赖配置的依赖。我们可以使用 dependencies()
和 allDependencies()
方法来获取依赖的引用,如下:
到目前为止,我们已经为具有完整版本号的依赖项显式设置了版本。要设置最小版本号,我们可以使用特殊的动态版本语法。例如,要将依赖项的版本设置为最低2.1
,我们使用版本值 2.1.+
. Gradle 会将依赖关系解析为版本 2.1
之后的最新版本或版本 2.1
本身。在下面的例子中,我们将至少定义一个对 spring-core
版本的 3.1
的依赖:
我们还可以使用 latest.integration
引用模块的最新发布版本。我们还可以设置具有最小和最大版本号的版本范围。下表显示了我们可以使用的范围:
范围 |
说明 |
|
大于等于 1.0 且小于等于 2.0 的所有版本 |
|
所有大于等于 1.0 且低于 2.0 的版本 |
|
所有大于 1.0 且低于或等于 2.0 的版本 |
|
所有高于 1.0 和低于 2.0 的版本 |
|
所有大于或等于 1.0 的版本 |
|
所有大于 1.0 的版本 |
|
所有低于或等于 2.0 的版本 |
|
所有低于 2.0 的版本 |
以下示例构建文件将使用版本 4.2.3.RELEASE
作为最新版本,该版本大于 4.2
并且小于比 4.3
:
如果我们有一个项目有很多依赖,并且这些依赖具有传递依赖,那么很容易出现版本冲突。如果一个模块依赖于 sample:logging:1.0
而另一个依赖于 sample:logging:2.0
,Gradle 将使用默认为最新版本号。
为了改变默认行为,我们设置了依赖配置的 resolutionStrategy
属性。如果出现冲突,我们可以指示 Gradle 使构建失败。这对于调试版本冲突非常有用。
在以下示例构建文件中,如果所有配置都出现版本冲突,我们会指示 Gradle 使构建失败:
要强制某个版本号用于所有依赖项(甚至是传递依赖项),我们可以使用 resolutionStrategy<的
force()
方法/代码>。使用这种方法,我们可以确保给定模块始终使用首选版本:
我们可以在Gradle 构建文件。 Gradle 使用 Groovy 的 AntBuilder 进行 ANT 集成。但是,如果我们想使用可选的 ANT 任务,我们必须做一些额外的事情,因为可选任务及其依赖项不在 Gradle 类路径中。幸运的是,我们只需要在 build.gradle
文件中定义可选任务的依赖关系,然后我们就可以定义和使用可选的 ANT 任务。
在以下示例中,我们使用 scp
ANT 可选任务。我们定义了一个名为 sshAntTask
的新配置,并将依赖项分配给该配置。然后,我们可以定义任务并将 classpath
属性设置为配置的 classpath
。我们使用 asPath
属性为ANT 任务转换 configurations
类路径。在示例中,我们还将看到如何在脚本运行时询问用户输入。 SSH 密钥文件的 passphrase
是一个秘密,我们不想将其保存在某个文件中,因此我们向用户询问。 System.console()
Java 方法返回对控制台的引用,通过 readPassword()
方法,我们可以得到 passphrase,
的值,如下:
In this chapter, we discussed dependency management support in Gradle. We also saw how to create a dependency configuration or use dependency configurations provided by a plugin.
为了获得真正的依赖工件及其传递依赖,我们必须定义存储这些文件的存储库。 Gradle 允许使用非常灵活的存储库配置。
最后,我们看到了如何为依赖配置定义实际的依赖。我们讨论了如何解决依赖项之间的版本冲突并在 Gradle 构建中使用这些依赖项。
在下一章中,我们将了解如何为我们的代码运行测试并从我们的构建中执行 Java 应用程序。我们还将讨论如何将我们自己的项目发布到存储库。