读书笔记《gradle-effective-implementations-guide-second-edition》使用Gradle for Java项目
在 Gradle 中,我们可以将插件应用到我们的项目中。插件基本上为我们的项目添加了额外的功能,例如任务和属性。通过使用插件,功能与核心 Gradle 构建逻辑分离。我们可以编写自己的插件,但 Gradle 还附带了开箱即用的插件。例如,Gradle 有一个 Java 插件。这个插件为我们的项目添加了编译、测试和打包 Java 源代码的任务。
与 Gradle 版本一起打包的插件永远不会针对该版本进行更新或更改,因此如果向插件添加新功能,则会发布全新的 Gradle 版本。在 Gradle 的未来版本中,这将发生变化。这不适用于我们自己编写的插件。我们可以发布我们自己的插件的新版本,独立于 Gradle 版本。让我们从作为 Gradle 发行版一部分的 Java 插件开始。
Java 插件提供了许多有用的任务和属性,我们可以使用它们来构建 Java 应用程序或库。如果我们遵循插件的约定优于配置的支持,我们不必在我们的 Gradle 构建文件中编写大量代码来使用它。如果我们愿意,我们仍然可以添加额外的配置选项来覆盖插件定义的默认约定。
让我们从一个新的构建文件开始并使用 Java 插件。我们只需要为我们的构建应用插件:
而已!只需添加这个简单的行,我们现在就可以在 Java 项目中使用很多任务。要查看插件添加的任务,我们在命令行上运行 tasks
命令并查看输出:
如果我们查看任务列表,我们可以看到现在可供我们使用的任务数量,这是我们以前没有的;所有这一切都是通过在我们的构建文件中添加一个简单的行来完成的。
我们有几个任务组,它们有各自的任务,可以使用。我们在 构建任务
部分有与构建源代码和打包相关的任务。 javadoc
任务用于生成 Javadoc 文档,位于 Documentation tasks
部分。运行测试和检查代码质量的任务在 验证任务
部分。最后,我们有几个基于规则的任务来构建、上传和清理 Java 项目中的工件或任务。
Java插件添加的任务是我们项目新添加功能的可见部分。但是,该插件还将所谓的 convention
对象添加到我们的项目中。
convention
对象有几个属性和方法,由插件的任务使用。这些属性和方法被添加到我们的项目中,可以像普通的项目属性和方法一样访问。因此,通过约定对象,我们不仅可以查看插件中任务使用的属性,还可以更改属性的值来重新配置某些任务。
要使用 Java 插件,我们首先要创建一个非常简单的 Java 源文件。然后我们可以使用插件的任务来构建源文件。您可以根据需要使此应用程序变得复杂,但为了保持主题,我们将使其尽可能简单。
通过应用 Java 插件,我们现在必须遵循项目目录结构的一些约定。要构建源代码,我们的 Java 源文件必须位于相对于项目目录的 src/main/java
目录中。如果我们有非 Java 源文件需要包含在 JAR 文件中,我们必须将它们放在 src/main/resources
目录中。我们的测试源文件需要在 src/test/java
目录下,任何测试需要的非Java源文件都可以放在 src/test/resources
.如果我们想要或需要,可以更改这些约定,但最好坚持使用它们,这样我们就不必在构建文件中编写任何额外的代码,这可能会导致错误。
我们将编写的示例 Java 项目是一个 Java 类,它使用外部属性文件来获取欢迎消息。名为Sample.java
的源文件位于 src/main/java
目录下,如下:
在代码中,我们使用 ResourceBundle.getBundle()
来阅读我们的欢迎信息。欢迎消息本身定义在一个名为 messages.properties
的 properties
文件中,该文件将进入 < code class="literal">src/main/resources 目录:
为了编译 Java 源文件并处理属性文件,我们运行 classes
任务。请注意,Java 插件已添加了 classes
任务。这就是 Gradle 中所谓的生命周期任务。 classes
任务实际上依赖于另外两个任务——compileJava
和 processResources
。当我们使用
--all
命令行选项运行 tasks
命令时,我们可以看到这个任务依赖:
让我们从命令行运行 classes
任务:
在这里,我们可以看到 compileJava
和 processResources
任务被执行,因为 classes< /code> 任务取决于这些任务。编译后的类文件和属性文件现在位于
build/classes/main
和 build/resources/main
目录中。 build
目录是 Gradle 用于构建输出文件的默认目录。
如果我们再次执行 classes
任务,我们会注意到这些任务支持 Gradle 的增量构建功能。由于我们没有更改 Java 源文件或属性文件,并且输出仍然存在,因此可以跳过所有任务,因为它们是最新的:
为了打包我们的类文件和属性文件,我们调用 jar
任务。该任务也是由 Java 插件添加的,并且依赖于 classes
任务。这意味着如果我们运行 jar
任务,那么 classes
任务也会被执行。让我们尝试运行 jar
任务,如下:
生成的 JAR 文件的默认名称是我们项目的名称。所以如果我们的项目叫做sample
,那么JAR文件就叫做 sample.jar
。我们可以在 build/libs
目录中找到该文件。如果我们查看 JAR 文件的内容,我们会看到已编译的类文件和 messages.properties
文件。此外, jar
任务会自动添加一个清单文件:
我们还可以执行 assemble
任务来创建 JAR 文件。 assemble
任务,另一个生命周期任务,依赖于 jar
任务,可以被其他插件扩展。我们还可以添加对其他任务的依赖项,这些任务为 JAR 文件以外的项目创建包,例如 WAR 文件或 ZIP 存档文件:
要重新开始并清理之前任务生成的所有输出,我们可以使用 clean
任务。此任务会删除项目 build
目录以及该目录中所有生成的文件。所以,如果我们从命令行执行 clean
任务,Gradle会删除 build
目录:
请注意,Java 插件还添加了一些基于规则的任务。其中之一是 clean<TaskName>
。我们可以使用此任务来删除特定任务的输出文件。 clean
任务删除完整的 build
目录;但是使用 clean<TaskName>
,我们只删除了命名任务创建的文件和目录。例如,为了清理 compileJava
任务生成的Java类文件,我们执行 cleanCompileJava
任务。由于这是一项基于规则的任务,Gradle 将确定 clean
之后的所有内容都必须是我们项目中的有效任务。此任务创建的文件和目录然后由 Gradle 确定并删除:
Java 插件还为我们的项目添加了一个新概念——源集。源集是一起编译和执行的源文件的集合。这些文件可以是 Java 源文件或资源文件。源集可以用来在我们的项目中将具有一定意义的文件分组在一起,而不必创建一个单独的项目。例如,我们可以将描述我们 Java 项目 API 的源文件的位置分隔在一个源集中,并运行仅适用于该源集中文件的任务。
在没有任何配置的情况下,我们已经有了 Java 插件添加的 main
和test
源集。对于每个源集,该插件还添加了以下三个任务: compile
, process
<SourceSet>类
。当源集命名为
main
时,我们在执行任务时不必提供源集名称。例如,
compileJava
适用于
main
源代码测试,但
compileTestJava< /code> 适用于
test
源集。
每个源集还具有一些属性来访问构成源集的目录和文件。下表显示了我们可以在源集中访问的属性:
源集属性 |
类型 |
说明 |
|
|
这些是该项目的 Java 源文件。此集合中只有具有 |
|
|
默认情况下,这与 Java 属性相同,因此它包含所有 Java 源文件。其他插件可以将额外的源文件添加到此集合中。 |
|
|
这些是此源集的所有资源文件。这包含资源源目录中的所有文件,不包括任何具有 |
|
|
默认情况下,这是资源和 Java 属性的组合。这包括该源集的所有源文件,包括资源和 Java 源文件。 |
|
|
这些是源集中源文件的输出文件。这包含已编译的类和已处理的资源。 |
|
|
这些是包含 Java 源文件的目录。 |
|
|
这些是包含此源集的资源文件的目录。 |
|
|
这是包含此源集中 Java 源文件的已编译类文件的输出目录。 |
|
|
这是包含来自此源集中的资源的已处理资源文件的输出目录。 |
|
|
这是带有源集名称的只读值。 |
我们可以通过项目的 sourceSets
属性访问这些属性。在以下示例中,我们将创建一个新任务来显示多个属性的值:
当我们运行 sourceSetJavaproperties
任务时,我们得到以下输出:
我们可以在项目中创建自己的源集。源集包含所有相互关联的源文件。在我们的示例中,我们将添加一个新的源集以包含一个 Java 接口。我们的Sample
类将实现接口;但是,由于我们使用单独的源集,我们可以稍后使用它来创建一个单独的 JAR 文件,其中仅包含已编译的接口类。我们将源集命名为 api
,因为接口实际上是我们示例项目的API,我们可以与其他项目共享。
要定义这个源集,我们只需要将名称放在项目的 sourceSets
属性中,如下所示:
Gradle 将基于此源集创建三个新任务——apiClasses
、compileApiJava
和处理ApiResources
。执行 tasks
命令后我们可以看到这些任务:
我们在 src/api/java
目录中创建了我们的 Java 接口,该目录是 api< 的 Java 源文件的源目录/code> 源集。下面的代码可以让我们看到Java接口:
要编译源文件,我们可以执行 compileApiJava
或 apiClasses
任务:
源文件编译在 build/classes/api
目录下。
现在我们将更改我们的Sample
类的源代码并实现 ReadWelcomeMessage
接口,如下代码所示:
接下来,我们运行 classes
任务来重新编译我们更改的 Java 源文件:
我们得到一个编译错误! Java 编译器找不到 ReadWelcomeMessage
接口。但是,我们只是运行了 apiClasses
任务并编译了界面而没有错误。
为了解决这个问题,我们必须定义 classes
和 apiClasses
任务之间的依赖关系。 classes
任务依赖于 apiClasses
任务。首先,必须编译接口,然后必须编译实现该接口的类。
接下来,我们必须将编译后的接口类文件的输出目录添加到main
源集的compileClasspath
属性中。完成此操作后,我们确定 Java 编译器会选择已编译的类文件来编译 Sample
类。
为此,我们将更改构建文件并添加两个任务和主源集配置之间的任务依赖关系,如下所示:
现在我们可以再次运行 classes 任务,没有错误:
如果我们将 Gradle 用于现有项目,我们可能具有与 Gradle 定义的默认结构不同的目录结构,或者我们可能出于其他原因想要具有不同的结构。我们可以通过配置源集并为源目录使用不同的值来解决这个问题。
考虑我们有一个具有以下源目录结构的项目:
我们需要重新配置 main
和 test
源集,但我们还必须添加一个新的 集成测试
源集。以下代码反映了源集的目录结构:
请注意我们必须如何将 integration-test
源集的名称放在引号中;这是因为我们在名称中使用了连字符。然后,Gradle 将源集的名称转换为 integrationTest
(不带连字符并带有大写 T< /跨度>)。例如,要编译集成 test
源集的源文件,我们使用 compileIntegrationTestJava
任务。
我们已经讨论过 Java 插件将任务和源集添加到我们的 Gradle 项目中;但是,我们也获得了许多可以使用的新属性。插件的自定义属性在 org.gradle.api.plugins.Convention
类型的 Convention
对象中设置。插件使用Convention
对象来公开我们可以在项目中使用的属性和方法。插件的 Convention
对象被添加到项目的 convention
属性中。 Gradle 项目的 convention
属性是插件中所有 Convention
对象的容器。
我们可以直接从插件的 Convention
对象中访问属性作为项目属性,也可以指定 Convention
对象的完整路径插件以获取属性或调用方法。
例如,sourceSets
属性是 Java 插件的 Convention
对象的属性。通过以下任务, showConvention
,我们看到了访问此属性的不同方式:
要查看我们可用的所有属性,我们必须从命令行调用 properties
任务。以下输出显示了 properties
任务的部分输出:
如果我们查看列表,我们会看到很多属性,我们可以使用这些属性来重新定义 compile
或 test 的输出文件所在的目录
任务被存储。下表显示了目录属性:
属性名称 |
默认值 |
说明 |
|
|
这是相对于构建目录的目录名称,用于存储分发文件 |
|
|
这是存储生成的 JAR 文件的 |
|
|
这是用于存储有关依赖项的缓存信息的目录的名称;它是相对于构建目录的 |
|
|
这是存储生成文档的目录名称;它是相对于构建目录的 |
|
|
这是相对于构建目录的目录名称,用于存储测试报告 |
|
|
这存储了测试结果 XML 文件;它是相对于构建目录的 |
Java 插件还为我们的项目添加了其他属性。这些属性可用于设置 Java 版本的源和目标兼容性以编译 Java 源文件或设置生成的 JAR 文件的基本文件名。
下表显示了 Java 插件的约定属性:
属性名称 |
类型 |
默认值 |
说明 |
|
|
项目名称 |
这是用于归档任务创建的归档的基本文件名,例如 JAR |
|
|
用于运行 Gradle 的 Java 版本的 JDK |
这是使用 |
|
|
|
这是为 Java 类文件生成的版本 |
|
|
- |
这些是项目的源集 |
|
|
空清单 |
这是要包含在所有 JAR 文件中的清单 |
|
|
空列表 |
这是项目中创建的所有 JAR 文件的 |
在我们的示例项目中,我们已经看到生成的 JAR 文件是以项目名称命名的;但是通过 archivesBaseName
属性,我们可以改变它。我们还可以将项目的源代码兼容性更改为 Java 6。最后,我们还可以更改用于生成的 JAR 文件的清单。以下构建文件反映了所有更改:
现在,如果我们调用 assemble
任务来创建我们的 JAR 文件并查看 build/libs
目录,我们可以看到JAR 文件现在命名为 gradle-sample-1.0.jar
:
要查看生成的清单文件的内容,我们将首先从 JAR 文件中提取文件,然后查看内容:
要生成Javadoc文档,我们必须使用org.gradle.api.tasks.javadoc.Javadoc
的javadoc
任务类型。该任务为 main
源集中的 Java 源文件生成文档。如果我们想为项目中的源集生成文档,我们必须配置 javadoc
任务或添加额外的 javadoc
我们项目的任务。
请注意,在我们的项目中,我们有一个包含 Java 源文件的 API 和主源集。如果我们想为两个源集生成文档,我们必须在我们的项目中配置 javadoc
任务。 javadoc
任务的 source
属性设置为 sourceSets.main.allJava
默认情况下。如果我们将 sourceSets.api.allJava
添加到 source
属性中,我们的接口文件也会被 javadoc
任务:
接下来,我们可以运行 javadoc
任务并生成文档并放入 build/docs/javadoc
目录:
我们可以在 javadoc
任务上设置更多属性。例如,我们可以使用 title
属性为生成的文档设置标题。默认值为项目名称,后跟项目版本号(如果有)。
要更改目标目录,我们可以将 javadoc
任务的destinationDir
属性设置为我们想要的目录。
我们还可以使用 options
属性来定义很多我们从 Java SDK javadoc
工具中知道的属性。以下示例向我们展示了如何为项目中的 javadoc
任务设置一些选项:
如果我们想将新 API 源集的输出打包到 JAR 文件中,我们必须自己定义一个新任务。 Gradle 并没有提供一些魔法来自动为我们做这件事。幸运的是,任务本身非常简单:
apiJar
任务是一个 Jar
任务。我们定义了 appendix
属性,用于生成 JAR 文件的最终文件名。我们使用 from()
方法来指向我们的API 源集的输出目录,因此所有生成的输出都包含在JAR 文件中。当我们运行 apiJar
任务时, 中会生成一个新的 gradle-sample-api-1.0.jar
JAR文件;build/libs
目录,如下:
JAR 文件的基本名称是项目名称,类似于 jar
任务的名称。如果我们查看内容,我们会看到我们编译的 ReadWelcomeMessage
类文件:
另请注意,我们没有在 apiJar
和 apiClasses
任务之间定义任务依赖关系;但是当我们运行 apiJar
任务时,Gradle 会自动运行 apiClasses
任务。发生这种情况是因为我们使用了 sourceSets.api.output
属性来定义需要包含在 JAR 文件中的文件; Gradle 注意到了这一点,并确定了负责在 sourceSets.api.output
目录中创建内容的任务。 apiClasses
任务是编译Java源文件并将资源处理到构建目录的任务,因此Gradle会首先调用 apiClasses
任务在 apiJar
任务之前。