构建 Java 和 JVM 项目
简介
plugins {
`java-library`
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
version = "1.2.1"
plugins {
id 'java-library'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
version = '1.2.1'
通过应用 Java 库插件,您可以获得大量功能
-
一个
compileJava
任务,用于编译 src/main/java 下的所有 Java 源文件。 -
一个
compileTestJava
任务,用于 src/test/java 下的源文件。 -
一个
test
任务,用于运行 src/test/java 中的测试。 -
一个
jar
任务,将main
编译后的类和 src/main/resources 中的资源打包成一个名为 <project>-<version>.jar 的 JAR 文件。 -
一个
javadoc
任务,用于为main
类生成 Javadoc。
这不足以构建任何非平凡的 Java 项目 — 至少,您可能会有一些文件依赖项。但这意味您的构建脚本只需包含特定于您项目的信息。
尽管示例中的属性是可选的,但我们建议您在项目中指定它们。配置工具链可防止项目使用不同 Java 版本构建时出现问题。版本字符串对于跟踪项目进度很重要。默认情况下,项目版本也用于归档名称。 |
Java 库插件还将上述任务集成到标准 基本插件生命周期任务 中。
-
jar
附属于assemble
。 -
test
附属于check
。
本章的其余部分解释了根据您的要求自定义构建的不同途径。您稍后还将看到如何调整库、应用程序、Web 应用程序和企业应用程序的构建。
通过源集声明源文件
Gradle 的 Java 支持首次引入了构建基于源的项目的新概念:源集。主要思想是源文件和资源通常按类型逻辑分组,例如应用程序代码、单元测试和集成测试。每个逻辑组通常都有自己的文件依赖项、类路径等。重要的是,构成源集的文件不必位于同一目录中!
源集是一个强大的概念,它将编译的几个方面联系在一起。
-
源文件及其位置
-
编译类路径,包括任何所需的依赖项(通过 Gradle 配置)
-
编译后的类文件放置位置
您可以在此图中看到它们之间的关系

阴影框表示源集本身的属性。此外,Java 库插件会自动为定义的所有源集或插件创建一个编译任务 — 名为 compileSourceSetJava
— 以及几个 依赖配置。
main
源集大多数语言插件,包括 Java,都会自动创建一个名为 main
的源集,用于项目的生产代码。此源集是特殊的,因为其名称不包含在配置和任务的名称中,因此您只有 compileJava
任务和 compileOnly
和 implementation
配置,而不是 compileMainJava
、mainCompileOnly
和 mainImplementation
。
Java 项目通常包含源文件之外的资源,例如属性文件,这些资源可能需要处理 — 例如通过替换文件中的标记 — 并在最终 JAR 中打包。Java 库插件通过自动为每个定义的源集创建一个名为 processSourceSetResources
(或 main
源集的 processResources
)的专用任务来处理此问题。下图显示了源集如何与此任务配合使用

与之前一样,阴影框表示源集的属性,在本例中,它包含资源文件的位置以及它们被复制到的位置。
除了 main
源集之外,Java 库插件还定义了一个 test
源集,它代表项目的测试。此源集由运行测试的 test
任务使用。您可以在 Java 测试 章中了解有关此任务和相关主题的更多信息。
项目通常将此源集用于单元测试,但如果您愿意,也可以将其用于集成、验收和其他类型的测试。替代方法是为您的其他每种测试类型定义一个新的源集,这通常出于以下一个或两个原因
-
您希望将测试彼此分开,以提高美观性和可管理性
-
不同测试类型需要不同的编译或运行时类路径或设置中的其他差异
您可以在 Java 测试章中看到这种方法的示例,它向您展示了如何在项目中设置集成测试。
您将在以下内容中了解有关源集及其提供功能的更多信息
源集配置
创建源集时,它还会创建上述多个配置。构建逻辑**不应**尝试创建或访问这些配置,直到它们首先由源集创建。
创建源集时,如果其中一个自动创建的配置已经存在,并且其作用与源集将分配的作用不同,则构建将失败。
下面的构建演示了这种行为。
configurations {
val myCodeCompileClasspath: Configuration by creating
}
sourceSets {
val myCode: SourceSet by creating
}
configurations {
myCodeCompileClasspath
}
sourceSets {
myCode
}
在这种情况下,构建将失败。
遵循两个简单的最佳实践将避免此问题
-
不要创建源集将使用的名称的配置,例如以
Api
、Implementation
、ApiElements
、CompileOnly
、CompileOnlyApi
、RuntimeOnly
、RuntimeClasspath
或RuntimeElements
结尾的名称。(此列表并非详尽无遗。) -
在任何自定义配置之前创建任何自定义源集。
请记住,每当您在 configurations
容器中引用配置时(无论是否提供初始化操作),Gradle 都会创建该配置。有时,在使用 Groovy DSL 时,这种创建并不明显,如下例所示,其中 myCustomConfiguration
在调用 extendsFrom
之前创建。
configurations {
myCustomConfiguration.extendsFrom(implementation)
}
管理依赖项
绝大多数 Java 项目都依赖库,因此管理项目依赖项是构建 Java 项目的重要组成部分。依赖管理是一个很大的主题,因此我们在此处重点介绍 Java 项目的基础知识。如果您想深入了解细节,请查看 依赖管理简介。
指定 Java 项目的依赖项只需三部分信息
-
您需要哪个依赖项,例如名称和版本。
-
它需要什么,例如编译或运行。
-
去哪里寻找它。
前两个在 dependencies {}
块中指定,第三个在 repositories {}
块中指定。例如,要告诉 Gradle 您的项目需要 3.6.7 版本的 Hibernate Core 来编译和运行您的生产代码,并且您想从 Maven Central 仓库下载该库,您可以使用以下片段
repositories {
mavenCentral()
}
dependencies {
implementation("org.hibernate:hibernate-core:3.6.7.Final")
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.hibernate:hibernate-core:3.6.7.Final'
}
Gradle 中这三个元素的术语如下
-
仓库 (例如:
mavenCentral()
) — 在哪里查找您声明为依赖项的模块。 -
配置 (例如:
implementation
) — 一组命名依赖项,为了特定目标(例如编译或运行模块)而分组在一起 — 一种更灵活的 Maven 范围形式。 -
模块坐标 (例如:
org.hibernate:hibernate-core-3.6.7.Final
) — 依赖项的 ID,通常采用 '<group>:<module>:<version>' 的形式(或 Maven 术语中的 '<groupId>:<artifactId>:<version>')。
您可以在此处找到更全面的依赖管理术语词汇表。
就配置而言,最主要的是
-
compileOnly
— 编译生产代码所必需但不能作为运行时类路径一部分的依赖项。 -
implementation
(取代compile
) — 用于编译和运行时。 -
runtimeOnly
(取代runtime
) — 仅在运行时使用,不用于编译。 -
testCompileOnly
— 与compileOnly
相同,但用于测试。 -
testImplementation
—implementation
的测试等效项。 -
testRuntimeOnly
—runtimeOnly
的测试等效项。
您可以在 插件参考章 中了解有关这些内容以及它们之间关系的更多信息。
请注意,Java 库插件 提供了另外两个配置 — api
和 compileOnlyApi
— 用于编译模块和任何依赖于它的模块所需的依赖项。
compile
配置?Java 库插件历来使用 compile
配置来表示编译和运行项目生产代码所需的依赖项。它现在已被弃用,使用时会发出警告,因为它不区分影响 Java 库项目公共 API 的依赖项和不影响公共 API 的依赖项。您可以在构建 Java 库中了解此区别的重要性。
我们在这里仅仅触及了皮毛,因此我们建议您在熟悉使用 Gradle 构建 Java 项目的基础知识后,阅读专门的依赖管理章节。需要进一步阅读的一些常见场景包括
-
使用 本地文件系统目录 中的依赖项
-
声明兄弟项目作为依赖项
-
通过复合构建测试对第三方依赖项的修复(发布到和使用Maven Local的更好替代方案)
您会发现 Gradle 拥有丰富的 API 用于处理依赖项 — 掌握它需要时间,但对于常见场景而言,使用起来很简单。
编译代码
如果您遵循约定,编译生产代码和测试代码会非常容易
-
将生产源代码放在 src/main/java 目录下。
-
将测试源代码放在 src/test/java 目录下。
-
在
compileOnly
或implementation
配置中声明生产编译依赖项(参见上一节)。 -
在
testCompileOnly
或testImplementation
配置中声明测试编译依赖项。 -
为生产代码运行
compileJava
任务,为测试运行compileTestJava
。
其他 JVM 语言插件,例如 Groovy 的插件,遵循相同的约定模式。我们建议您尽可能遵循这些约定,但您不必这样做。有几种自定义选项,您将在下面看到。
自定义文件和目录位置
假设您有一个使用 src 目录作为生产代码,使用 test 目录作为测试代码的遗留项目。传统的目录结构将无法工作,因此您需要告诉 Gradle 在哪里可以找到源文件。您可以通过源集配置来完成此操作。
每个源集都定义其源代码所在位置,以及资源和类文件的输出目录。您可以使用以下语法覆盖约定值
sourceSets {
main {
java {
setSrcDirs(listOf("src"))
}
}
test {
java {
setSrcDirs(listOf("test"))
}
}
}
sourceSets {
main {
java {
srcDirs = ['src']
}
}
test {
java {
srcDirs = ['test']
}
}
}
现在 Gradle 将只在 src 和 test 中直接搜索相应的源代码。如果您不想覆盖约定,而只是想 添加 一个额外的源目录,也许其中包含一些您想分开的第三方源代码怎么办?语法类似
sourceSets {
main {
java {
srcDir("thirdParty/src/main/java")
}
}
}
sourceSets {
main {
java {
srcDir 'thirdParty/src/main/java'
}
}
}
关键是,我们在这里使用 方法 srcDir()
来追加目录路径,而设置 srcDirs
属性则会替换任何现有值。这是 Gradle 中的常见约定:设置属性会替换值,而相应的方法会追加值。
您可以在 SourceSet 和 SourceDirectorySet 的 DSL 参考中查看源集上所有可用的属性和方法。请注意,srcDirs
和 srcDir()
都位于 SourceDirectorySet
上。
更改编译器选项
大多数编译器选项都可以通过相应的任务访问,例如 compileJava
和 compileTestJava
。这些任务属于 JavaCompile 类型,因此请阅读任务参考以获取最新和全面的选项列表。
例如,如果您想为编译器使用单独的 JVM 进程并防止编译失败导致构建失败,您可以使用此配置
tasks.compileJava {
options.isIncremental = true
options.isFork = true
options.isFailOnError = false
}
compileJava {
options.incremental = true
options.fork = true
options.failOnError = false
}
这也是您可以更改编译器详细程度、禁用字节码中的调试输出以及配置编译器可以找到注解处理器的地方。
定位特定 Java 版本
默认情况下,Gradle 会将 Java 代码编译为运行 Gradle 的 JVM 的语言级别。如果编译时需要定位特定的 Java 版本,Gradle 提供了多种选项
使用工具链
当 Java 代码使用特定工具链编译时,实际编译由指定 Java 版本的编译器执行。编译器提供对所需 Java 语言版本的语言功能和 JDK API 的访问。
在最简单的情况下,可以通过 java
扩展为项目配置工具链。这样,不仅编译从中受益,而且 test
和 javadoc
等其他任务也将一致地使用相同的工具链。
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
您可以在 Java 工具链 指南中了解有关此内容的更多信息。
使用 Java 发布版本
Gradle 支持从 Java 10 开始使用 release 标志。它可以在编译任务上配置,如下所示。
tasks.compileJava {
options.release = 7
}
compileJava {
options.release = 7
}
release 标志提供了与工具链类似的保证。它验证 Java 源不使用在更高 Java 版本中引入的语言特性,并且代码不访问来自更新 JDK 的 API。编译器生成的字节码也与请求的 Java 版本相对应,这意味着编译后的代码无法在较旧的 JVM 上执行。
Java 编译器的 release
选项是在 Java 9 中引入的。然而,由于 Java 9 中的一个 bug,只有从 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 编译、测试、生成 Javadocs 和执行应用程序。不支持 Java 5 及更低版本。
如果使用 Java 10+,利用 release 标志可能是一个更简单的解决方案,请参见上文。 |
要使用 Java 6 或 Java 7,需要配置以下任务
-
JavaCompile
任务,用于分支并使用正确的 Java home -
Javadoc
任务,用于使用正确的javadoc
可执行文件 -
Test
和JavaExec
任务,用于使用正确的java
可执行文件
使用 Java 工具链,可以按如下方式完成
java {
toolchain {
languageVersion = JavaLanguageVersion.of(7)
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(7)
}
}
唯一的要求是安装 Java 7,并且它必须位于 Gradle 可以自动检测的位置 或 明确配置 的位置。
独立编译独立源
大多数项目至少有两组独立的源:生产代码和测试代码。Gradle 已经将此场景作为其 Java 约定的一部分,但如果您有其他源集怎么办?最常见的场景之一是您有某种形式的独立集成测试。在这种情况下,自定义源集可能正是您所需要的。
您可以在 Java 测试章 中查看设置集成测试的完整示例。您可以以相同的方式设置承担不同角色的其他源集。那么问题就来了:什么时候应该定义自定义源集?
要回答这个问题,请考虑源是否
-
需要使用独特的类路径进行编译
-
生成与
main
和test
类处理方式不同的类 -
构成项目的自然组成部分
如果您对 3 和其他两者之一的回答都是肯定的,那么自定义源集可能是正确的方法。例如,集成测试通常是项目的一部分,因为它们测试 main
中的代码。此外,它们通常要么拥有自己独立于 test
源集的依赖项,要么需要使用自定义 Test
任务运行。
其他常见场景不太明确,可能存在更好的解决方案。例如
-
单独的 API 和实现 JAR — 将它们作为单独的项目可能更有意义,特别是如果您已经有多项目构建
-
生成的源 — 如果生成的源应与生产代码一起编译,请将其路径添加到
main
源集,并确保compileJava
任务依赖于生成源的任务
如果您不确定是否要创建自定义源集,那么请继续创建。它应该很简单,如果不是,那么它可能不是适合这项工作的工具。
调试编译错误
Gradle 为编译失败提供详细报告。
如果编译任务失败,错误摘要将显示在以下位置
-
任务的输出,提供错误的即时上下文。
-
构建输出底部的“出了什么问题”摘要,与所有其他失败合并,便于参考。
* What went wrong:
Execution failed for task ':project1:compileJava'.
> Compilation failed; see the compiler output below.
Java compilation warning
sample-project/src/main/java/Problem1.java:6: warning: [cast] redundant cast to String
var warning = (String)"warning";
^
Java compilation error
sample-project/src/main/java/Problem2.java:6: error: incompatible types: int cannot be converted to String
String a = 1;
^
此报告功能与 —continue
标志一起使用。
管理资源
许多 Java 项目除了源文件之外还使用资源,例如图像、配置文件和本地化数据。有时这些文件只需要原封不动地打包,有时它们需要作为模板文件或其他方式进行处理。无论哪种方式,Java 库插件都会为每个源集添加一个特定的 Copy 任务,用于处理其关联的资源。
任务名称遵循 processSourceSetResources
的约定 — 或者 main
源集的 processResources
— 并且它会自动将 src/[sourceSet]/resources 中的任何文件复制到将包含在生产 JAR 中的目录中。此目标目录也将包含在测试的运行时类路径中。
由于 processResources
是 ProcessResources
任务的一个实例,因此您可以执行 使用文件 章中描述的任何处理。
Java 属性文件和可复现构建
您可以通过 WriteProperties 任务轻松创建 Java 属性文件,这解决了 Properties.store()
的一个已知问题,该问题会降低 增量构建 的实用性。
用于写入属性文件的标准 Java API 每次都会生成一个唯一的文件,即使使用相同的属性和值,因为它在注释中包含时间戳。如果没有任何属性发生变化,Gradle 的 WriteProperties
任务会逐字节生成完全相同的输出。这是通过对属性文件的生成方式进行一些调整来实现的
-
输出中不添加时间戳注释
-
行分隔符与系统无关,但可以明确配置(默认为
'\n'
) -
属性按字母顺序排序
有时,在不同的机器上以字节对字节的方式重新创建归档可能是可取的。您希望确保从源代码构建的工件无论何时何地构建,都能产生相同的字节对字节结果。这对于 reproducible-builds.org 等项目是必要的。
这些调整不仅带来了更好的增量构建集成,而且还有助于可复现构建。本质上,可复现构建保证您无论何时或在哪个系统上运行构建执行,都会看到相同的结果 — 包括测试结果和生产二进制文件。
运行测试
除了在 src/test/java 中提供单元测试的自动编译外,Java 库插件还原生支持运行使用 JUnit 3、4 和 5(JUnit 5 支持 在 Gradle 4.6 中推出)以及 TestNG 的测试。您将获得
-
一个类型为 Test 的自动
test
任务,使用test
源集。 -
一个包含所有运行的
Test
任务结果的 HTML 测试报告。 -
轻松过滤要运行的测试。
-
对测试运行方式的细粒度控制。
-
创建自己的测试执行和测试报告任务的机会。
您不会为您声明的每个源集获得一个 Test
任务,因为并非每个源集都代表测试!这就是为什么如果您不能将它们包含在 test
源集中,您通常需要为集成测试和验收测试等创建自己的 Test
任务。
由于测试方面有很多内容,因此该主题在 自己的章 中进行了介绍,其中我们讨论了
-
测试如何运行
-
如何通过过滤运行测试子集
-
Gradle 如何发现测试
-
如何配置测试报告并添加您自己的报告任务
-
如何利用特定的 JUnit 和 TestNG 功能
您还可以在 Test 的 DSL 参考中了解有关配置测试的更多信息。
打包和发布
如何打包和潜在地发布您的 Java 项目取决于它的项目类型。库、应用程序、Web 应用程序和企业应用程序都有不同的要求。在本节中,我们将重点介绍 Java 库插件提供的基本功能。
默认情况下,Java 库插件提供 jar
任务,该任务将所有编译后的生产类和资源打包到一个 JAR 中。此 JAR 也由 assemble
任务自动构建。此外,插件可以配置为提供 javadocJar
和 sourcesJar
任务,如果需要,可以打包 Javadoc 和源代码。如果使用了发布插件,这些任务将在发布期间自动运行,或者可以直接调用。
java {
withJavadocJar()
withSourcesJar()
}
java {
withJavadocJar()
withSourcesJar()
}
如果您想创建一个“uber”(也称为“fat”)JAR,那么您可以使用这样的任务定义
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) }
})
}
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。请注意,为了正确发布 JAR,您需要在此处使用 archiveClassifier
而不是 archiveAppendix
。
您可以使用其中一个发布插件来发布 Java 项目创建的 JAR
修改 JAR 清单
Jar
、War
和 Ear
任务的每个实例都有一个 manifest
属性,允许您自定义进入相应存档的 MANIFEST.MF 文件。以下示例演示了如何设置 JAR 清单中的属性
tasks.jar {
manifest {
attributes(
"Implementation-Title" to "Gradle",
"Implementation-Version" to archiveVersion
)
}
}
jar {
manifest {
attributes("Implementation-Title": "Gradle",
"Implementation-Version": archiveVersion)
}
}
有关其提供的配置选项,请参阅 Manifest。
您还可以创建 Manifest
的独立实例。这样做的原因之一是在 JAR 之间共享清单信息。以下示例演示了如何在 JAR 之间共享通用属性
val sharedManifest = java.manifest {
attributes (
"Implementation-Title" to "Gradle",
"Implementation-Version" to version
)
}
tasks.register<Jar>("fooJar") {
manifest = java.manifest {
from(sharedManifest)
}
}
def sharedManifest = java.manifest {
attributes("Implementation-Title": "Gradle",
"Implementation-Version": version)
}
tasks.register('fooJar', Jar) {
manifest = java.manifest {
from sharedManifest
}
}
您可以使用的另一个选项是将清单合并到单个 Manifest
对象中。这些源清单可以采用文本形式或其他 Manifest
对象。在以下示例中,除 sharedManifest
(来自上一个示例的 Manifest
对象)外,所有源清单都是文本文件
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()
}
})
}
}
}
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()
,您可以随时使用它轻松地将清单写入磁盘,如下所示
tasks.jar { manifest.writeTo(layout.buildDirectory.file("mymanifest.mf")) }
tasks.named('jar') { manifest.writeTo(layout.buildDirectory.file('mymanifest.mf')) }
生成 API 文档
Java 库插件提供了一个类型为 Javadoc 的 javadoc
任务,它将为您的所有生产代码(即 main
源集中的任何源)生成标准 Javadocs。该任务支持 Javadoc 参考文档 中描述的核心 Javadoc 和标准 doclet 选项。有关这些选项的完整列表,请参阅 CoreJavadocOptions 和 StandardJavadocDocletOptions。
举个例子,假设您想在 Javadoc 注释中使用 Asciidoc 语法。为此,您需要将 Asciidoclet 添加到 Javadoc 的 doclet 路径中。这是一个演示此操作的示例
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")
}
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 文档。
tasks.register<Javadoc>("testJavadoc") {
source = sourceSets.test.get().allJava
}
tasks.register('testJavadoc', Javadoc) {
source = sourceSets.test.allJava
}
这只是您可能会遇到的两个非平凡但常见的自定义。
构建 JVM 组件
所有特定的 JVM 插件都构建在 Java 插件 之上。上面的示例仅说明了此基本插件提供并与所有 JVM 插件共享的概念。
继续阅读以了解哪些插件适合哪种项目类型,因为建议选择特定插件而不是直接应用 Java 插件。
构建 Java 库
库项目的独特之处在于它们被其他 Java 项目使用(或“消费”)。这意味着随 JAR 文件发布的依赖项元数据(通常以 Maven POM 的形式)至关重要。特别是,库的消费者应该能够区分两种不同类型的依赖项:那些只需要编译库的依赖项和那些也需要编译消费者的依赖项。
Gradle 通过 Java 库插件 管理这种区别,该插件除了本章中介绍的 implementation 配置之外,还引入了 api 配置。如果某个依赖项的类型出现在您的库公共类的公共字段或方法中,则该依赖项通过您的库的公共 API 公开,因此应添加到 api 配置中。否则,该依赖项是内部实现细节,应添加到 implementation 中。
构建 Java Web 应用程序
Java Web 应用程序可以根据您使用的技术以多种方式打包和部署。例如,您可以使用带有 fat JAR 的 Spring Boot 或在 Netty 上运行的基于 Reactive 的系统。无论您使用何种技术,Gradle 及其庞大的插件社区都将满足您的需求。但是,核心 Gradle 仅直接支持部署为 WAR 文件的传统基于 Servlet 的 Web 应用程序。
这种支持通过 War 插件 提供,该插件会自动应用 Java 插件并添加一个额外的打包步骤,执行以下操作
-
将 src/main/webapp 中的静态资源复制到 WAR 的根目录。
-
将编译后的生产类复制到 WAR 的 WEB-INF/classes 子目录中。
-
将库依赖项复制到 WAR 的 WEB-INF/lib 子目录中。
这由 war
任务完成,该任务有效地替换了 jar
任务 — 尽管该任务仍然存在 — 并附加到 assemble
生命周期任务。有关更多详细信息和配置选项,请参阅插件的章。
没有核心支持直接从构建运行您的 Web 应用程序,但我们确实建议您尝试 Gretty 社区插件,它提供了一个嵌入式 Servlet 容器。
构建 Java EE 应用程序
多年来,Java 企业系统发生了很大变化,但如果您仍然部署到 JEE 应用程序服务器,您可以使用 Ear 插件。这增加了构建 EAR 文件的约定和任务。插件的章有更多详细信息。
启用 Java 预览功能
使用 Java 预览功能很可能会使您的代码与未编译预览功能的代码不兼容。因此,我们强烈建议您不要发布使用预览功能编译的库,并将预览功能的使用限制在玩具项目中。 |
要为编译、测试执行和运行时启用 Java 预览功能,您可以使用以下 DSL 片段
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.add("--enable-preview")
}
tasks.withType<Test>().configureEach {
jvmArgs("--enable-preview")
}
tasks.withType<JavaExec>().configureEach {
jvmArgs("--enable-preview")
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs += "--enable-preview"
}
tasks.withType(Test).configureEach {
jvmArgs += "--enable-preview"
}
tasks.withType(JavaExec).configureEach {
jvmArgs += "--enable-preview"
}
构建其他 JVM 语言项目
如果您想利用 JVM 的多语言特性,这里描述的大部分内容仍然适用。
语言之间的编译依赖
这些插件在 Groovy/Scala 编译和 Java 编译(源集中 java
文件夹中的源代码)之间创建了一个依赖项。您可以通过调整相关编译任务的类路径来更改此默认行为,如以下示例所示
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)
}
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)
}
-
通过将
compileGroovy
类路径仅设置为sourceSets.main.compileClasspath
,我们有效地移除了之前通过让类路径也考虑sourceSets.main.java.classesDirectory
而声明的对compileJava
的依赖。 -
通过将
sourceSets.main.groovy.classesDirectory
添加到compileJava
classpath
中,我们有效地声明了对compileGroovy
任务的依赖。
所有这些都通过使用 目录属性 变为可能。