Gradle 使用约定优于配置的方法来构建基于 JVM 的项目,该项目借用了 Apache Maven 的多种约定。特别是,它对源文件和资源使用相同的默认目录结构,并且可以与 Maven 兼容的存储库配合使用。

我们将在本章中详细介绍 Java 项目,但大多数主题也适用于其他受支持的 JVM 语言,例如KotlinGroovyScala。如果您没有太多使用 Gradle 构建基于 JVM 的项目的经验,请查看Java 示例,获取有关如何构建各种类型的基本 Java 项目的分步说明。

本节中的示例使用 Java 库插件。然而,所描述的功能是所有 JVM 插件共享的。不同插件的具体信息可在其专用文档中找到。

您可以探索许多JavaGroovyScalaKotlin 的实践示例。

介绍

Java 项目最简单的构建脚本应用Java 库插件,并可选择设置项目版本并选择要使用的Java 工具链:

build.gradle.kts
plugins {
    `java-library`
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

version = "1.2.1"
build.gradle
plugins {
    id 'java-library'
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

version = '1.2.1'

通过应用 Java 库插件,您可以获得一系列功能:

  • 编译src/main/javacompileJava下所有 Java 源文件的任务

  • src/test/javacompileTestJava下源文件的任务

  • 从src/test/javatest运行测试的任务

  • 将src/main/resources中已编译的类和资源jar打包到名为<project>-<version>.jar的单个 JAR 中的任务main

  • 为类javadoc生成 Javadoc 的任务main

这不足以构建任何重要的 Java 项目——至少,您可能会有一些文件依赖项。但这意味着您的构建脚本只需要特定于您的项目的信息。

尽管示例中的属性是可选的,但我们建议您在项目中指定它们。配置工具链可以防止使用不同 Java 版本构建的项目出现问题。版本字符串对于跟踪项目的进度非常重要。默认情况下,项目版本也用于存档名称。

Java Library Plugin 还将上述任务集成到标准Base Plugin 生命周期任务中:

  • jar附于assemble

  • test附于check

本章的其余部分解释了根据您的要求定制构建的不同途径。稍后您还将看到如何调整库、应用程序、Web 应用程序和企业应用程序的构建。

通过源集声明您的源文件

Gradle 的 Java 支持首先引入了构建基于源代码的项目的新概念:源集。主要思想是源文件和资源通常按类型进行逻辑分组,例如应用程序代码、单元测试和集成测试。每个逻辑组通常都有自己的文件依赖项、类路径等集。值得注意的是,构成源集的文件不必位于同一目录中

源集是一个强大的概念,它将编译的几个方面联系在一起:

  • 源文件及其所在位置

  • 编译类路径,包括任何必需的依赖项(通过 Gradle配置

  • 编译后的类文件放置的位置

您可以在此图中看到它们之间的相互关系:

java源集编译
图 1. 源集和 Java 编译

阴影框表示源集本身的属性。最重要的是,Java 库插件会自动为您或插件定义的每个源集创建一个编译任务(已命名)以及多个依赖项配置compileSourceSetJava

main

大多数语言插件(包括 Java)都会自动创建一个名为 的源集main,用于项目的生产代码。该源集的特殊之处在于,它的名称不包含在配置和任务的名称中,因此您只有任务compileJavacompileOnly配置,implementation而不是分别具有和 和。compileMainJavamainCompileOnlymainImplementation

Java 项目通常包括源文件以外的资源,例如属性文件,这些资源可能需要处理(例如通过替换文件中的标记)并打包到最终的 JAR 中。 Java 库插件通过自动为每个定义的调用的源集(或源集)创建专用任务来处理此问题。下图显示了源集如何适应此任务:processSourceSetResourcesprocessResourcesmain

javasourcesets进程资源
图 2. 处理源集的非源文件

与之前一样,阴影框表示源集的属性,在本例中包括资源文件的位置及其复制到的位置。

除了main源集之外,Java 库插件还定义了test代表项目测试的源集。该源集由test运行测试的任务使用。您可以在Java 测试章节中了解有关此任务和相关主题的更多信息。

项目通常使用此源集进行单元测试,但如果您愿意,您也可以将其用于集成、验收和其他类型的测试。另一种方法是为每个其他测试类型定义一个新的源集,这通常是出于以下一个或两个原因:

  • 为了美观和可管理性,您希望将测试彼此分开

  • 不同的测试类型需要不同的编译或运行时类路径或设置中的其他差异

您可以在 Java 测试章节中看到此方法的示例,该示例向您展示了如何在项目中设置集成测试。

您将了解有关源集及其提供的功能的更多信息:

源集配置

创建源集时,它还会创建许多如上所述的配置。在源集首次创建这些配置之前,构建逻辑不应尝试创建或访问这些配置。

创建源集时,如果这些自动创建的配置之一已存在,Gradle 将发出弃用警告。如果现有配置的角色与源集分配的角色不同,则其角色将更改为正确的值,并且将发出另一个弃用警告。

下面的构建演示了这种不需要的行为。

build.gradle.kts
configurations {
    val myCodeCompileClasspath: Configuration by creating
}

sourceSets {
    val myCode: SourceSet by creating
}
build.gradle
configurations {
    myCodeCompileClasspath
}

sourceSets {
    myCode
}

在这种情况下,会发出以下弃用警告:

When creating configurations during sourceSet custom setup, Gradle found that configuration customCompileClasspath already exists with permitted usage(s):
	Consumable - this configuration can be selected by another project as a dependency
	Resolvable - this configuration can be resolved by this project to a set of files
	Declarable - this configuration can have dependencies added to it
Yet Gradle expected to create it with the usage(s):
	Resolvable - this configuration can be resolved by this project to a set of files

以下两个简单的最佳实践将避免此问题:

  1. 不要使用源集将使用的名称创建配置,例如以、ApiImplementationApiElementsCompileOnlyCompileOnlyApiRuntimeOnly结尾的名称。 (此列表并不详尽。)RuntimeClasspathRuntimeElements

  2. 在任何自定义配置之前创建任何自定义源集。

请记住,任何时候您在容器内引用配置configurations(无论是否提供初始化操作),Gradle 都会创建该配置。有时,当使用 Groovy DSL 时,这种创建并不明显,如下面的示例所示,wheremyCustomConfiguration是在调用extendsFrom.

build.gradle
configurations {
    myCustomConfiguration.extendsFrom(implementation)
}

有关详细信息,请参阅不要预期配置创建

管理您的依赖项

绝大多数 Java 项目都依赖于库,因此管理项目的依赖关系是构建 Java 项目的重要部分。依赖管理是一个很大的话题,所以我们在这里将重点关注 Java 项目的基础知识。如果您想深入了解细节,请查看依赖管理简介

指定 Java 项目的依赖项只需要三部分信息:

  • 您需要哪个依赖项,例如名称和版本

  • 它需要什么,例如编译或运行

  • 在哪里寻找它

前两个在dependencies {}块中指定,第三个在repositories {}块中指定。例如,要告诉 Gradle 您的项目需要 3.6.7 版本的Hibernate Core 来编译和运行您的生产代码,并且您想要从 Maven 中央存储库下载该库,您可以使用以下片段:

build.gradle.kts
repositories {
    mavenCentral()
}

dependencies {
    implementation("org.hibernate:hibernate-core:3.6.7.Final")
}
build.gradle
repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.hibernate:hibernate-core:3.6.7.Final'
}

这三个元素的 Gradle 术语如下:

  • 存储库(例如:mavenCentral())——在哪里查找您声明为依赖项的模块

  • 配置(例如:implementation)——命名的依赖项集合,为了特定目标(例如编译或运行模块)而分组在一起——一种更灵活的 Maven 范围形式

  • 模块坐标(ex: ) — 依赖项的 ID,通常采用 ' <group> : <module> : <version>org.hibernate:hibernate-core-3.6.7.Final '形式(或Maven 术语中的' <groupId> : <artifactId> : <version> ')

您可以在此处找到更全面的依赖管理术语表。

就配置而言,主要感兴趣的是:

  • compileOnly- 对于编译生产代码所必需的依赖项,但不应成为运行时类路径的一部分

  • implementation(取代compile) — 用于编译和运行时

  • runtimeOnly(取代runtime) — 仅在运行时使用,不用于编译

  • testCompileOnlycompileOnly- 与除了用于测试之外相同

  • testImplementation— 测试等效于implementation

  • testRuntimeOnly— 测试等效于runtimeOnly

您可以在插件参考章节中了解有关这些内容以及它们之间如何相互关联的更多信息。

请注意,Java 库插件提供了两种额外的配置 -apicompileOnlyApi- 用于编译模块和依赖于它的任何模块所需的依赖项。

为什么没有compile配置?

Java 库插件历来使用compile编译和运行项目生产代码所需的依赖项配置。它现已被弃用,并且在使用时会发出警告,因为它不区分影响 Java 库项目公共 API 的依赖项和不影响的依赖项。您可以在构建 Java 库中详细了解这种区别的重要性。

我们在这里只触及了表面,因此我们建议您在熟悉了使用 Gradle 构建 Java 项目的基础知识后,再阅读专门的依赖关系管理章节。需要进一步阅读的一些常见场景包括:

您会发现 Gradle 拥有丰富的 API 来处理依赖项——需要一些时间来掌握,但对于常见场景来说使用起来很简单。

编译你的代码

如果遵循以下约定,编译生产代码和测试代码会变得非常简单:

  1. 将您的生产源代码放在src/main/java目录下

  2. 将您的测试源代码放在src/test/java下

  3. compileOnly在或配置中声明您的生产编译依赖项implementation(请参阅上一节)

  4. testCompileOnly在或testImplementation配置中声明您的测试编译依赖项

  5. 运行compileJava生产代码和compileTestJava测试的任务

其他 JVM 语言插件(例如Groovy的插件)遵循相同的约定模式。我们建议您尽可能遵循这些约定,但并非必须这样做。有多种自定义选项,您将在接下来看到。

自定义文件和目录位置

想象一下,您有一个遗留项目,它使用src目录来存储生产代码,并使用 test来存储测试代码。传统的目录结构不起作用,因此您需要告诉 Gradle 在哪里可以找到源文件。您可以通过源集配置来完成此操作。

每个源集定义其源代码所在的位置,以及类文件的资源和输出目录。您可以使用以下语法覆盖约定值:

build.gradle.kts
sourceSets {
    main {
        java {
            setSrcDirs(listOf("src"))
        }
    }

    test {
        java {
            setSrcDirs(listOf("test"))
        }
    }
}
build.gradle
sourceSets {
    main {
         java {
            srcDirs = ['src']
         }
    }

    test {
        java {
            srcDirs = ['test']
        }
    }
}

现在 Gradle 只会直接在src中搜索并测试相应的源代码。如果您不想覆盖约定,而只是想添加一个额外的源目录(也许包含一些您想要单独保留的第三方源代码),该怎么办?语法类似:

build.gradle.kts
sourceSets {
    main {
        java {
            srcDir("thirdParty/src/main/java")
        }
    }
}
build.gradle
sourceSets {
    main {
        java {
            srcDir 'thirdParty/src/main/java'
        }
    }
}

至关重要的是,我们使用此处的方法 srcDir()附加目录路径,而设置srcDirs属性会替换任何现有值。这是 Gradle 中的常见约定:设置属性会替换值,而相应的方法会附加值。

您可以在SourceSetSourceDirectorySet的 DSL 参考中查看源集上可用的所有属性和方法。请注意,srcDirssrcDir()均处于打开状态SourceDirectorySet

更改编译器选项

大多数编译器选项都可以通过相应的任务访问,例如compileJavacompileTestJava。这些任务的类型为JavaCompile,因此请阅读任务参考以获取最新且全面的选项列表。

例如,如果您想为编译器使用单独的 JVM 进程并防止编译失败而导致构建失败,则可以使用以下配置:

build.gradle.kts
tasks.compileJava {
    options.isIncremental = true
    options.isFork = true
    options.isFailOnError = false
}
build.gradle
compileJava {
    options.incremental = true
    options.fork = true
    options.failOnError = false
}

这也是您可以更改编译器的详细程度、禁用字节代码中的调试输出以及配置编译器可以找到注释处理器的位置的方法。

针对特定 Java 版本

默认情况下,Gradle 会将 Java 代码编译到运行 Gradle 的 JVM 的语言级别。如果编译时需要针对特定​​版本的 Java,Gradle 提供了多个选项:

  1. 使用Java 工具链是定位语言版本的首选方法。
    工具链统一处理编译、执行和Javadoc生成,并且可以在项目级别进行配置。

  2. release从 Java 10 开始可以使用属性
    。选择 Java 版本可确保使用配置的语言级别并针对该 Java 版本中的 JDK API 完成编译。

  3. 使用sourceCompatibilitytargetCompatibility属性。
    尽管通常不建议这样做,但这些选项历史上曾用于在编译期间配置 Java 版本。

使用工具链

当使用特定工具链编译Java代码时,实际编译是由指定Java版本的编译器执行的。编译器提供对所请求的 Java 语言版本的语言功能和 JDK API 的访问。

在最简单的情况下,可以使用扩展为项目配置工具链java。这样,不仅编译可以从中受益,其他任务(例如test和 )javadoc也将始终使用相同的工具链。

build.gradle.kts
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}
build.gradle
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

您可以在Java 工具链指南中了解更多相关信息。

使用Java发行版

设置发布标志可确保使用指定的语言级别,无论哪个编译器实际执行编译。要使用此功能,编译器必须支持所请求的发行版本。可以在使用更新的工具链进行编译时指定较早的发行版本。

Gradle 支持使用 Java 10 中的发布标志。它可以在编译任务上进行如下配置。

build.gradle.kts
tasks.compileJava {
    options.release = 7
}
build.gradle
compileJava {
    options.release = 7
}

发布标志提供类似于工具链的保证。它验证 Java 源代码没有使用后续 Java 版本中引入的语言功能,并且代码不会访问较新的 JDK 中的 API。编译器生成的字节码也与请求的 Java 版本相对应,这意味着编译后的代码无法在较旧的 JVM 上执行。

Java 编译器选项是在 Java 9 中引入的。但是,由于Java 9 中的错误release,只能从 Java 10 开始在 Gradle 中使用此选项。

使用 Java 兼容性选项

使用兼容性属性可能会导致执行编译代码时出现运行时故障,因为它们提供的保证较弱。相反,请考虑使用工具链发布标志。

sourceCompatibility选项targetCompatibility对应于 Java 编译器选项-source-target。它们被认为是针对特定 Java 版本的遗留机制。但是,这些选项不能防止使用后续 Java 版本中引入的 API。

sourceCompatibility

定义源文件中使用的 Java 语言版本。

targetCompatibility

定义代码应运行的最低 JVM 版本,即它确定编译器生成的字节码的版本。

可以使用具有相同名称的属性为每个JavaCompile任务设置这些选项,也可以在所有编译任务的扩展上设置这些选项。java { }

针对 Java 6 和 Java 7

Gradle 本身只能在 Java 版本 8 或更高版本的 JVM 上运行。但是,Gradle 仍然支持 Java 6 和 Java 7 的编译、测试、生成 Javadoc 和执行应用程序。不支持 Java 5 及更低版本。

如果使用 Java 10+,利用该release标志可能是一个更简单的解决方案,请参见上文。

要使用Java 6或Java 7,需要配置以下任务:

  • JavaCompile分叉并使用正确的 Java home 的任务

  • Javadoc使用正确的javadoc可执行文件的任务

  • Test以及JavaExec使用正确的java可执行文件的任务。

通过使用 Java 工具链,可以按如下方式完成此操作:

build.gradle.kts
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(7)
    }
}
build.gradle
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(7)
    }
}

唯一的要求是 Java 7 已安装,并且必须位于Gradle 可以自动检测或显式配置的位置

单独编译独立源

大多数项目至少有两组独立的源:生产代码和测试代码。 Gradle 已经将此场景纳入其 Java 约定的一部分,但如果您有其他源集怎么办?最常见的场景之一是当您进行某种形式的单独集成测试时。在这种情况下,自定义源集可能正是您所需要的。

您可以在Java 测试章节中看到设置集成测试的完整示例。您可以以相同的方式设置履行不同角色的其他源集。那么问题就变成了:什么时候应该定义自定义源集?

要回答这个问题,请考虑来源是否:

  1. 需要使用唯一的类路径进行编译

  2. 生成处理方式与main和不同的test

  3. 成为项目的自然组成部分

如果您对 3 个以及其他任何一个的答案都是肯定的,那么自定义源集可能是正确的方法。例如,集成测试通常是项目的一部分,因为它们测试main.此外,它们通常具有独立于test源集的自己的依赖项,或者需要与自定义Test任务一起运行。

其他常见场景不太明确,可能有更好的解决方案。例如:

  • 单独的 API 和实现 JAR - 将它们作为单独的项目可能是有意义的,特别是如果您已经有一个多项目构建

  • 生成的源 - 如果生成的源应使用生产代码进行编译,请将其路径添加到main源集,并确保任务compileJava依赖于生成源的任务

如果您不确定是否创建自定义源集,请继续创建。它应该是简单的,如果不是,那么它可能不是适合这项工作的工具。

管理资源

许多 Java 项目都使用源文件之外的资源,例如图像、配置文件和本地化数据。有时,这些文件只需原样打包,有时需要作为模板文件或以其他方式处理。无论哪种方式,Java 库插件都会为每个源集添加一个特定的复制任务,以处理其关联资源。

该任务的名称遵循- 或源集 -的约定,并且它将自动将src/[sourceSet]/resources中的任何文件复制到将包含在生产 JAR 中的目录。该目标目录也将包含在测试的运行时类路径中。processSourceSetResourcesprocessResourcesmain

由于processResources是该任务的一个实例,因此您可以执行“使用文件”ProcessResources一章中描述的任何处理。

Java 属性文件和可重现的构建

您可以通过WriteProperties任务轻松创建 Java 属性文件,该任务修复了一个众所周知的问题,该问题可能会降低增量构建Properties.store()的有用性。

用于写入属性文件的标准 Java API 每次都会生成一个唯一的文件,即使使用相同的属性和值也是如此,因为它在注释中包含时间戳。如果没有任何属性发生更改, Gradle 的WriteProperties任务会逐字节生成完全相同的输出。这是通过对属性文件的生成方式进行一些调整来实现的:

  • 输出中未添加时间戳注释

  • 行分隔符与系统无关,但可以显式配置(默认为'\n'

  • 属性按字母顺序排序

有时可能需要在不同的机器上以逐字节的方式重新创建存档。您希望确保从源代码构建工件会产生相同的结果,逐字节生成,无论它是何时何地构建的。这对于像 reproducible-builds.org 这样的项目是必要的。

这些调整不仅可以带来更好的增量构建集成,而且还有助于可重复的构建。从本质上讲,可重现的构建保证您将从构建执行中看到相同的结果 - 包括测试结果和生产二进制文件 - 无论何时或在什么系统上运行它。

运行测试

除了在src/test/java中提供单元测试的自动编译之外,Java 库插件还原生支持运行使用 JUnit 3、4 和 5(Gradle 4.6 中提供了JUnit 5 支持)和 TestNG 的测试。你得到:

  • 使用源集的Testtest类型的自动任务test

  • 包含所有 Test运行任务的结果的 HTML 测试报告

  • 轻松过滤要运行的测试

  • 对测试运行方式的细粒度控制

  • 有机会创建您自己的测试执行和测试报告任务

不会Test为声明的每个源集获得任务,因为并非每个源集都代表测试!这就是为什么您通常需要为集成和验收测试等任务创建自己的Test任务(如果它们无法包含在test源集中)。

由于测试涉及很多内容,因此该主题有其自己的章节,我们将在其中查看:

  • 测试如何运行

  • 如何通过过滤运行测试子集

  • Gradle 如何发现测试

  • 如何配置测试报告并添加您自己的报告任务

  • 如何利用特定的 JUnit 和 TestNG 功能

您还可以在Test的 DSL 参考中了解有关配置测试的更多信息。

包装出版

如何打包和发布 Java 项目取决于项目的类型。库、应用程序、Web 应用程序和企业应用程序都有不同的要求。在本节中,我们将重点关注 Java 库插件提供的基本框架。

默认情况下,Java Library Plugin 提供jar将所有已编译的生产类和资源打包到单个 JAR 中的任务。这个 JAR 也是由任务自动构建的assemble。此外,如果需要,可以配置该插件来提供打包 Javadoc 和源代码的javadocJar任务。sourcesJar如果使用发布插件,这些任务将在发布过程中自动运行或可以直接调用。

build.gradle.kts
java {
    withJavadocJar()
    withSourcesJar()
}
build.gradle
java {
    withJavadocJar()
    withSourcesJar()
}

如果您想创建一个“uber”(又名“fat”)JAR,那么您可以使用如下任务定义:

build.gradle.kts
plugins {
    java
}

version = "1.0.0"

repositories {
    mavenCentral()
}

dependencies {
    implementation("commons-io:commons-io:2.6")
}

tasks.register<Jar>("uberJar") {
    archiveClassifier = "uber"

    from(sourceSets.main.get().output)

    dependsOn(configurations.runtimeClasspath)
    from({
        configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) }
    })
}
build.gradle
plugins {
    id 'java'
}

version = '1.0.0'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'commons-io:commons-io:2.6'
}

tasks.register('uberJar', Jar) {
    archiveClassifier = 'uber'

    from sourceSets.main.output

    dependsOn configurations.runtimeClasspath
    from {
        configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) }
    }
}

有关可用配置选项的更多详细信息,请参阅Jar 。请注意,您需要使用archiveClassifier而不是archiveAppendix此处来正确发布 JAR。

您可以使用发布插件之一来发布由 Java 项目创建的 JAR:

修改 JAR 清单

JarWar和任务的每个实例Ear都有一个manifest属性,允许您自定义进入相应存档的MANIFEST.MF文件。以下示例演示了如何在 JAR 清单中设置属性:

build.gradle.kts
tasks.jar {
    manifest {
        attributes(
            "Implementation-Title" to "Gradle",
            "Implementation-Version" to archiveVersion
        )
    }
}
build.gradle
jar {
    manifest {
        attributes("Implementation-Title": "Gradle",
                   "Implementation-Version": archiveVersion)
    }
}

请参阅清单以了解它提供的配置选项。

您还可以创建 的独立实例Manifest。这样做的原因之一是在 JAR 之间共享清单信息。以下示例演示了如何在 JAR 之间共享公共属性:

build.gradle.kts
val sharedManifest = java.manifest {
    attributes (
        "Implementation-Title" to "Gradle",
        "Implementation-Version" to version
    )
}

tasks.register<Jar>("fooJar") {
    manifest = java.manifest {
        from(sharedManifest)
    }
}
build.gradle
def sharedManifest = java.manifest {
    attributes("Implementation-Title": "Gradle",
               "Implementation-Version": version)
}
tasks.register('fooJar', Jar) {
    manifest = java.manifest {
        from sharedManifest
    }
}

您可以使用的另一个选项是将清单合并到单个Manifest对象中。这些源清单可以采用文本或其他对象的形式Manifest。在以下示例中,源清单都是文本文件,除了sharedManifest,它是Manifest上一个示例中的对象:

build.gradle.kts
tasks.register<Jar>("barJar") {
    manifest {
        attributes("key1" to "value1")
        from(sharedManifest, "src/config/basemanifest.txt")
        from(listOf("src/config/javabasemanifest.txt", "src/config/libbasemanifest.txt")) {
            eachEntry(Action<ManifestMergeDetails> {
                if (baseValue != mergeValue) {
                    value = baseValue
                }
                if (key == "foo") {
                    exclude()
                }
            })
        }
    }
}
build.gradle
tasks.register('barJar', Jar) {
    manifest {
        attributes key1: 'value1'
        from sharedManifest, 'src/config/basemanifest.txt'
        from(['src/config/javabasemanifest.txt', 'src/config/libbasemanifest.txt']) {
            eachEntry { details ->
                if (details.baseValue != details.mergeValue) {
                    details.value = baseValue
                }
                if (details.key == 'foo') {
                    details.exclude()
                }
            }
        }
    }
}

清单按照在语句中声明的顺序合并from。如果基本清单和合并清单都定义了同一键的值,则默认情况下合并清单获胜。您可以通过添加操作来完全自定义合并行为,eachEntry在这些操作中您可以访问生成清单的每个条目的ManifestMergeDetails实例。请注意,合并是延迟完成的,无论是在生成 JAR 时还是在调用Manifest.writeTo()或时。Manifest.getEffectiveManifest()

说到writeTo(),您可以随时使用它轻松地将清单写入磁盘,如下所示:

build.gradle.kts
tasks.jar { manifest.writeTo(layout.buildDirectory.file("mymanifest.mf")) }
build.gradle
tasks.named('jar') { manifest.writeTo(layout.buildDirectory.file('mymanifest.mf')) }

生成API文档

Java 库插件提供了Javadocjavadoc类型的任务,它将为您的所有生产代码(即源集中的任何源)生成标准 Javadoc 。该任务支持Javadoc 参考文档中描述的核心 Javadoc 和标准 doclet 选项。有关这些选项的完整列表,请参阅CoreJavadocOptionsStandardJavadocDocletOptions 。main

作为您可以执行的操作的示例,假设您想要在 Javadoc 注释中使用 Asciidoc 语法。为此,您需要将 Asciidoclet 添加到 Javadoc 的 doclet 路径中。这是一个执行此操作的示例:

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()
        }
    }
}

tasks.javadoc {
    dependsOn("configureJavadoc")
}
build.gradle
configurations {
    asciidoclet
}

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

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

javadoc {
    dependsOn configureJavadoc
}

您不必为此创建配置,但这是一种处理特定目的所需的依赖关系的优雅方法。

您可能还想创建自己的 Javadoc 任务,例如为测试生成 API 文档:

build.gradle.kts
tasks.register<Javadoc>("testJavadoc") {
    source = sourceSets.test.get().allJava
}
build.gradle
tasks.register('testJavadoc', Javadoc) {
    source = sourceSets.test.allJava
}

这些只是您可能遇到的两个重要但常见的自定义。

清理构建

Java Library Pluginclean通过应用Base Plugin将任务添加到您的项目中。此任务只是删除layout.buildDirectory目录中的所有内容,因此您应该始终将构建生成的文件放在那里。该任务是删除dir的一个实例,您可以通过设置其属性来更改它删除的目录。

构建 JVM 组件

所有特定的 JVM 插件都构建在Java 插件之上。上面的示例仅说明了该基本插件提供的概念并与所有 JVM 插件共享。

请继续阅读以了解哪些插件适合哪种项目类型,因为建议选择特定的插件而不是直接应用 Java 插件。

构建 Java 库

库项目的独特之处在于它们被其他 Java 项目使用(或“消耗”)。这意味着与 JAR 文件一起发布的依赖元数据(通常以 Maven POM 的形式)至关重要。特别是,您的库的使用者应该能够区分两种不同类型的依赖项:仅编译您的库所需的依赖项和编译使用者也需要的依赖项。

Gradle 通过Java Library Plugin管理这种区别,除了本章介绍的实现之外,它还引入了api配置。如果依赖项中的类型出现在库的公共类的公共字段或方法中,则该依赖项将通过库的公共 API 公开,因此应将其添加到api配置中。否则,依赖项是内部实现细节,应添加到实现中。

如果您不确定 API 和实现依赖项之间的区别,Java 库插件一章有详细的解释。此外,您还可以探索构建 Java 库的基本实用示例

构建 Java 应用程序

打包为 JAR 的 Java 应用程序无法从命令行或桌面环境轻松启动。应用程序插件通过创建一个包含生产 JAR、其依赖项以及类 Unix 和 Windows 系统启动脚本的发行版来解决命令行方面的问题。

有关更多详细信息,请参阅插件的章节,但以下是您所获得内容的快速摘要:

  • assemble创建应用程序的 ZIP 和 TAR 发行版,其中包含运行该应用程序所需的所有内容

  • run从构建启动应用程序的任务(为了方便测试)

  • 用于启动应用程序的 Shell 和 Windows 批处理脚本

您可以在相应的示例中查看构建 Java 应用程序的基本示例。

构建 Java Web 应用程序

Java Web 应用程序可以通过多种方式打包和部署,具体取决于您使用的技术。例如,您可以将Spring Boot与 fat JAR 或在Netty上运行的基于Reactive的系统结合使用。无论您使用什么技术,Gradle 及其庞大的插件社区都将满足您的需求。不过,Core Gradle 仅直接支持部署为 WAR 文件的传统基于 Servlet 的 Web 应用程序。

该支持来自War Plugin,它会自动应用 Java 插件并添加一个额外的打包步骤,该步骤执行以下操作:

  • 将静态资源从src/main/webapp复制到 WAR 的根目录中

  • 将编译后的生产类复制到WAR 的WEB-INF/classes子目录中

  • 将库依赖项复制到WAR 的WEB-INF/lib子目录中

这是由war任务完成的,该任务有效地替换了该jar任务(尽管该任务仍然存在)并附加到assemble生命周期任务。有关更多详细信息和配置选项,请参阅插件的章节。

没有直接从构建运行 Web 应用程序的核心支持,但我们建议您尝试Gretty社区插件,它提供了嵌入式 Servlet 容器。

构建 Java EE 应用程序

Java 企业系统多年来发生了很大变化,但如果您仍在部署到 JEE 应用程序服务器,则可以使用Ear Plugin。这添加了构建 EAR 文件的约定和任务。该插件的章节有更多详细信息。

构建 Java 平台

Java 平台代表一组依赖声明和约束,它们形成了一个应用于消费项目的内聚单元。该平台没有来源,也没有自己的工件。它在 Maven 世界中映射到BOM

该支持通过Java 平台插件提供,该插件设置不同的配置和发布组件。

此插件是例外,因为它不应用 Java 插件。

启用 Java 预览功能

使用 Java 预览功能很可能会使您的代码与没有功能预览的编译代码不兼容。因此,我们强烈建议您不要发布使用预览功能编译的库,并将功能预览限制在玩具项目中。

要启用编译、测试执行和运行时的 Java预览功能,您可以使用以下 DSL 片段:

build.gradle.kts
tasks.withType<JavaCompile>().configureEach {
    options.compilerArgs.add("--enable-preview")
}

tasks.withType<Test>().configureEach {
    jvmArgs("--enable-preview")
}

tasks.withType<JavaExec>().configureEach {
    jvmArgs("--enable-preview")
}
build.gradle
tasks.withType(JavaCompile).configureEach {
    options.compilerArgs += "--enable-preview"
}

tasks.withType(Test).configureEach {
    jvmArgs += "--enable-preview"
}

tasks.withType(JavaExec).configureEach {
    jvmArgs += "--enable-preview"
}

构建其他 JVM 语言项目

如果您想利用 JVM 的多语言功能,此处描述的大部分内容仍然适用。

Gradle 本身提供了GroovyScala插件。这些插件自动应用对编译 Java 代码的支持,并且可以通过将它们与插件组合来进一步增强java-library

语言之间的编译依赖

java这些插件在 Groovy/Scala 编译和 Java 编译(源集文件夹中的源代码)之间创建依赖关系。您可以通过调整所涉及的编译任务的类路径来更改此默认行为,如下例所示:

build.gradle.kts
tasks.named<AbstractCompile>("compileGroovy") {
    // Groovy only needs the declared dependencies
    // (and not longer the output of compileJava)
    classpath = sourceSets.main.get().compileClasspath
}
tasks.named<AbstractCompile>("compileJava") {
    // Java also depends on the result of Groovy compilation
    // (which automatically makes it depend of compileGroovy)
    classpath += files(sourceSets.main.get().groovy.classesDirectory)
}
build.gradle
tasks.named('compileGroovy') {
    // Groovy only needs the declared dependencies
    // (and not longer the output of compileJava)
    classpath = sourceSets.main.compileClasspath
}
tasks.named('compileJava') {
    // Java also depends on the result of Groovy compilation
    // (which automatically makes it depend of compileGroovy)
    classpath += files(sourceSets.main.groovy.classesDirectory)
}
  1. 通过将compileGroovy类路径设置为 only sourceSets.main.compileClasspath,我们可以有效地消除先前对compileJava通过将类路径也考虑在内而声明的依赖关系sourceSets.main.java.classesDirectory

  2. 通过添加sourceSets.main.groovy.classesDirectory到,我们有效地声明了对任务compileJava classpath的依赖compileGroovy

所有这一切都可以通过使用目录属性来实现。

额外的语言支持

除了核心 Gradle 之外,还有其他适用于更多 JVM 语言的优秀插件!