Java 插件为项目添加了 Java 编译以及测试和打包功能。它作为许多其他 JVM 语言 Gradle 插件的基础。您可以在构建 Java 项目章节中找到 Java 插件的全面介绍和概述。

如上所述,该插件为 JVM 项目添加了基本构建块。它的功能集已被其他插件取代,这些插件根据您的项目类型提供了更多功能。您不应直接将其应用于您的项目,而应考虑使用 java-libraryapplication 插件,或其中一种受支持的替代 JVM 语言。

用法

要使用 Java 插件,请在您的构建脚本中包含以下内容

build.gradle.kts
plugins {
    java
}
build.gradle
plugins {
    id 'java'
}

任务

Java 插件为您的项目添加了许多任务,如下所示。

compileJavaJavaCompile

依赖于:所有对编译类路径有贡献的任务,包括通过项目依赖项在类路径上的项目的 jar 任务

使用 JDK 编译器编译生产 Java 源文件。

processResourcesProcessResources

将生产资源复制到生产资源目录中。

classes

依赖于compileJava, processResources

这是一个聚合任务,仅依赖于其他任务。其他插件可能会向其附加额外的编译任务。

compileTestJavaJavaCompile

依赖于classes,以及所有对测试编译类路径有贡献的任务

使用 JDK 编译器编译测试 Java 源文件。

processTestResourcesCopy

将测试资源复制到测试资源目录中。

testClasses

依赖于compileTestJava, processTestResources

这是一个聚合任务,仅依赖于其他任务。其他插件可能会向其附加额外的测试编译任务。

jarJar

依赖于classes

根据附加到 main 源集的类和资源,组装生产 JAR 文件。

javadocJavadoc

依赖于classes

使用 Javadoc 为生产 Java 源生成 API 文档。

testTest

依赖于testClasses,以及所有生成测试运行时类路径的任务

使用 JUnit 或 TestNG 运行单元测试。

cleanDelete

删除项目构建目录。

cleanTaskNameDelete

删除由指定任务创建的文件。例如,cleanJar 将删除由 jar 任务创建的 JAR 文件,cleanTest 将删除由 test 任务创建的测试结果。

源集任务

对于您添加到项目的每个源集,Java 插件都会添加以下任务

compileSourceSetJavaJavaCompile

依赖于:所有对源集编译类路径有贡献的任务

使用 JDK 编译器编译给定源集的 Java 源文件。

processSourceSetResourcesCopy

将给定源集的资源复制到资源目录中。

sourceSetClassesTask

依赖于compileSourceSetJava, processSourceSetResources

为打包和执行准备给定源集的类和资源。某些插件可能会为源集添加额外的编译任务。

生命周期任务

Java 插件将其一些任务附加到基础插件(Java 插件自动应用)定义的生命周期任务,并且还添加了一些其他生命周期任务

assemble

依赖于jar

聚合任务,组装项目中的所有归档文件。此任务由基础插件添加。

check

依赖于test

执行验证任务(例如运行测试)的聚合任务。一些插件会将自己的验证任务添加到 check。如果您希望自定义 Test 任务在完全构建时执行,也应将其附加到此生命周期任务。此任务由基础插件添加。

build

依赖于check, assemble

执行项目完整构建的聚合任务。此任务由基础插件添加。

buildNeeded

依赖于build,以及 testRuntimeClasspath 配置中作为依赖项的所有项目中的 buildNeeded 任务。

执行项目及其所有依赖项目的完整构建。

buildDependents

依赖于build,以及所有在其 testRuntimeClasspath 配置中将此项目作为依赖项的项目中的 buildDependents 任务

执行项目及其所有依赖于它的项目的完整构建。

buildConfigName — 任务规则

依赖于:所有生成附加到指定 — ConfigName — 配置的工件的任务

组装指定配置的工件。此规则由基础插件添加。

下图显示了这些任务之间的关系。

javaPluginTasks
图 1. Java 插件 - 任务

项目布局

Java 插件假定如下所示的项目布局。这些目录都不需要存在或包含任何内容。Java 插件将编译它找到的任何内容,并处理任何缺失的内容。

src/main/java

生产 Java 源文件。

src/main/resources

生产资源,例如 XML 和属性文件。

src/test/java

测试 Java 源文件。

src/test/resources

测试资源。

src/sourceSet/java

名为 sourceSet 的源集的 Java 源文件。

src/sourceSet/resources

名为 sourceSet 的源集的资源。

更改项目布局

您可以通过配置相应的源集来配置项目布局。这将在以下部分中更详细地讨论。这是一个更改主 Java 和资源源目录的简短示例。

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

源集

该插件添加了以下源集

main

包含项目的生产源代码,该代码被编译并组装成 JAR。

test

包含您的测试源代码,该代码使用 JUnit 或 TestNG 进行编译和执行。这些通常是单元测试,但只要它们都共享相同的编译和运行时类路径,您就可以在此源集中包含任何测试。

源集属性

下表列出了源集的一些重要属性。您可以在 SourceSet 的 API 文档中找到更多详细信息。

name — (只读) String

源集的名称,用于标识它。

output — (只读) SourceSetOutput

源集的输出文件,包含其编译后的类和资源。

output.classesDirs — (只读) FileCollection

默认值: layout.buildDirectory.dir("classes/java/$name"), 例如 build/classes/java/main

用于生成此源集类的目录。可能包含其他 JVM 语言的目录,例如 build/classes/kotlin/main

output.resourcesDir — File

默认值: layout.buildDirectory.dir("resources/$name"), 例如 build/resources/main

用于生成此源集资源的目录。

compileClasspath — FileCollection

默认值: ${name}CompileClasspath 配置

编译此源集源文件时使用的类路径。

annotationProcessorPath — FileCollection

默认值: ${name}AnnotationProcessor 配置

编译此源集源文件时使用的处理器路径。

runtimeClasspath — FileCollection

默认值: $output, ${name}RuntimeClasspath 配置

执行此源集类时使用的类路径。

java — (只读) SourceDirectorySet

此源集的 Java 源文件。仅包含在 Java 源目录中找到的 .java 文件,并排除所有其他文件。

java.srcDirs — Set<File>

默认值: src/$name/java, 例如 src/main/java

包含此源集的 Java 源文件的源目录。您可以将其设置为本节中描述的任何值。

java.destinationDirectory — DirectoryProperty

默认值: layout.buildDirectory.dir("classes/java/$name"), 例如 build/classes/java/main

生成编译后的 Java 源文件的目录。您可以将其设置为本节中描述的任何值。

resources — (只读) SourceDirectorySet

此源集的资源。仅包含资源,并排除在资源目录中找到的任何 .java 文件。其他插件,例如Groovy 插件,会从该集合中排除其他类型的文件。

resources.srcDirs — Set<File>

默认值: [src/$name/resources]

包含此源集资源的目录。您可以将其设置为本节中描述的任何类型的值。

allJava — (只读) SourceDirectorySet

默认值: 与 java 属性相同

此源集的所有 Java 文件。某些插件,例如 Groovy 插件,会将额外的 Java 源文件添加到此集合中。

allSource — (只读) SourceDirectorySet

默认值: resourcesjava 属性中所有内容的总和

此源集的所有语言的所有源文件。这包括所有资源文件和所有 Java 源文件。某些插件,例如 Groovy 插件,会将额外的源文件添加到此集合中。

定义新的源集

请参阅《Java & JVM 项目测试》章节中的集成测试示例

其他一些简单的源集示例

添加包含源集类的 JAR

build.gradle.kts
tasks.register<Jar>("intTestJar") {
    from(sourceSets["intTest"].output)
}
build.gradle
tasks.register('intTestJar', Jar) {
    from sourceSets.intTest.output
}

为源集生成 Javadoc

build.gradle.kts
tasks.register<Javadoc>("intTestJavadoc") {
    source(sourceSets["intTest"].allJava)
    classpath = sourceSets["intTest"].compileClasspath
}
build.gradle
tasks.register('intTestJavadoc', Javadoc) {
    source sourceSets.intTest.allJava
    classpath = sourceSets.intTest.compileClasspath
}

添加测试套件以运行源集中的测试

build.gradle.kts
tasks.register<Test>("intTest") {
    testClassesDirs = sourceSets["intTest"].output.classesDirs
    classpath = sourceSets["intTest"].runtimeClasspath
}
build.gradle
tasks.register('intTest', Test) {
    testClassesDirs = sourceSets.intTest.output.classesDirs
    classpath = sourceSets.intTest.runtimeClasspath
}

依赖管理

Java 插件为您的项目添加了许多依赖配置,如下所示。然后,诸如 compileJavatest 之类的任务会使用这些配置中的一个或多个来获取相应的文件并使用它们,例如通过将它们放在编译或运行时类路径上。

依赖配置

有关 defaultarchives 配置的信息,请查阅基础插件参考文档。

有关 apicompileOnlyApi 配置的信息,请查阅Java 库插件参考文档和Java 项目的依赖管理

依赖声明配置

implementation

表示项目主源集在编译时和运行时都需要的依赖项(即,仅实现依赖项)。

compileOnly

表示仅在编译时需要,不包含在运行时类路径中的依赖项(即,仅编译时依赖项,运行时不使用)。

runtimeOnly

表示仅在运行时需要,不包含在编译类路径中的依赖项(即,仅运行时需要的依赖项)。

testImplementation 扩展 implementation

表示项目测试源集在编译时和运行时都需要的依赖项(即,仅用于测试的实现依赖项)。

testCompileOnly

表示仅在项目测试源集编译时需要,不包含在运行时类路径中的依赖项(即,仅用于编译测试的额外依赖项,运行时不使用)。

testRuntimeOnly 扩展 runtimeOnly

表示仅在项目测试源集运行时需要的依赖项(即,仅用于运行测试的运行时依赖项)。

annotationProcessor

表示在项目源代码编译期间使用的注解处理器(即,编译期间使用的注解处理器)。

可解析配置

compileClasspath 扩展 compileOnly, implementation

表示编译主源时使用的类路径,包括来自 compileOnlyimplementation 配置的依赖项。由任务 compileJava 使用。

runtimeClasspath 扩展 runtimeOnly, implementation

表示运行主源时使用的类路径,包括来自 runtimeOnlyimplementation 配置的依赖项。由任务 run 使用。

testCompileClasspath 扩展 testCompileOnly, testImplementation

表示编译测试源时使用的类路径,包括来自 testCompileOnlytestImplementation 配置的依赖项。由任务 compileTestJava 使用。

testRuntimeClasspath 扩展 testRuntimeOnly, testImplementation

表示运行测试源时使用的类路径,包括来自 testRuntimeOnlytestImplementation 配置的依赖项。由任务 test 使用。

以下图表分别显示了源集和测试源集的依赖配置。您可以使用此图例来解释颜色

  • 蓝色背景 — 您可以针对该配置声明依赖项。

  • 绿色背景 — 该配置供任务使用,而不是供您声明依赖项。

  • 灰色背景 — 一个任务。

java main configurations
图 2. Java 插件 - 源集依赖配置
java test configurations
图 3. Java 插件 - 测试源集依赖配置

对于您添加到项目的每个源集,Java 插件都会添加以下依赖配置

源集依赖配置

sourceSetImplementation

给定源集的编译时依赖项。由 sourceSetCompileClasspath, sourceSetRuntimeClasspath 使用。

sourceSetCompileOnly

给定源集的仅编译时依赖项,运行时不使用。

sourceSetCompileClasspath 扩展 sourceSetCompileOnly, sourceSetImplementation

编译类路径,编译源时使用。由 compileSourceSetJava 使用。

sourceSetAnnotationProcessor

此源集编译期间使用的注解处理器。

sourceSetRuntimeOnly

给定源集的仅运行时依赖项。

sourceSetRuntimeClasspath 扩展 sourceSetRuntimeOnly, sourceSetImplementation

运行时类路径包含实现元素以及仅运行时元素。

贡献的扩展

Java 插件将 java 扩展添加到项目中。这允许在专用的 DSL 块中配置许多与 Java 相关的属性。

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

下面列出了 java 扩展中可用属性和 DSL 函数的简要说明。

工具链和兼容性

toolchain

供使用 JVM 工具(如编译和执行)的任务使用的Java 工具链。默认值:构建 JVM 工具链。

JavaVersion sourceCompatibility

编译 Java 源时使用的 Java 版本兼容性。默认值:此扩展中工具链的语言版本。
请注意,在大多数情况下,使用工具链优于使用兼容性设置。

JavaVersion targetCompatibility

生成类的 Java 版本。默认值:sourceCompatibility
请注意,在大多数情况下,使用工具链优于使用兼容性设置。

打包

withJavadocJar()

自动打包 Javadoc 并创建一个带有 artifact -javadoc.jar 的变体 javadocElements,该变体将成为发布的一部分。

withSourcesJar()

自动打包源代码并创建一个带有 artifact -sources.jar 的变体 sourceElements,该变体将成为发布的一部分。

目录属性

String reporting.baseDir

生成报告的目录名称,相对于构建目录。默认值:reports

(只读) File reportsDir

生成报告的目录。默认值:reporting.baseDirectory

String testResultsDirName

生成测试结果 .xml 文件的目录名称,相对于构建目录。默认值:test-results

(只读) File testResultsDir

生成测试结果 .xml 文件的目录。默认值:layout.buildDirectory.dir(testResultsDirName)

String testReportDirName

生成测试报告的目录名称,相对于报告目录。默认值:tests

(只读) File testReportDir

生成测试报告的目录。默认值:reportsDir/testReportDirName

String libsDirName

生成库的目录名称,相对于构建目录。默认值:libs

(只读) File libsDir

生成库的目录。默认值:layout.buildDirectory.dir(libsDirName)

String distsDirName

生成分发文件的目录名称,相对于构建目录。默认值:distributions

(只读) File distsDir

生成分发文件的目录。默认值:layout.buildDirectory.dir(distsDirName)

String docsDirName

生成文档的目录名称,相对于构建目录。默认值:docs

(只读) File docsDir

生成文档的目录。默认值:layout.buildDirectory.dir(docsDirName)

String dependencyCacheDirName

缓存源依赖信息相对于构建目录的目录名称。默认值:dependency-cache

其他属性

(只读) SourceSetContainer sourceSets

包含项目的源集。默认值:非空 SourceSetContainer

String archivesBaseName

用于归档文件(例如 JAR 或 ZIP 文件)的基本名称。默认值:projectName

Manifest manifest

要包含在所有 JAR 文件中的清单。默认值:空清单。

测试

有关详细信息,请参阅Java & JVM 项目测试章节。

发布

components.java

用于发布jar 任务创建的生产 JAR 的 SoftwareComponent。此组件包括 JAR 的运行时依赖信息。

另请参阅java 扩展

增量 Java 编译

Gradle 附带了一个复杂的增量 Java 编译器,默认情况下处于活动状态。

这为您带来了以下好处

  • 增量构建速度快得多。

  • 更改的类文件数量最少。不需要重新编译的类在输出目录中保持不变。一个真正有用的场景是使用 JRebel——更改的输出类越少,JVM 刷新类的速度就越快。

为了帮助您理解增量编译的工作原理,下面提供了一个高级概述

  • Gradle 将重新编译所有受更改影响的类。

  • 如果一个类已被更改或依赖于另一个受影响的类,则该类为受影响的。这无论其他类是在同一项目中、另一个项目中还是外部库中定义都有效。

  • 一个类的依赖项是通过其字节码中的类型引用或通过编译器插件进行符号分析来确定的。

  • 由于源保留注解在字节码中不可见,因此更改源保留注解将导致完全重新编译。

  • 您可以通过应用松散耦合等良好软件设计原则来提高增量编译性能。例如,如果您在具体类及其依赖项之间放置一个接口,则只有当接口更改时,依赖类才会重新编译,而不是当实现更改时。

  • 类分析在项目目录中缓存,因此清理结账后的第一次构建可能会较慢。考虑在您的构建服务器上关闭增量编译器。

  • 类分析也是存储在构建缓存中的输出,这意味着如果从构建缓存中获取编译输出,则增量编译分析也将被获取,并且下一次编译将是增量编译。

已知问题

  • 如果您使用读取资源(例如配置文件)的注解处理器,则需要将这些资源声明为编译任务的输入。

  • 如果资源文件发生更改,Gradle 将触发完全重新编译。

  • 使用自定义 executablejavaHome 会禁用一些优化。编译任务在编译错误后或 Java 常量更改后不会立即使用增量构建。如果可能,请改用工具链

  • 源结构与包名称不匹配,虽然对于编译是合法的,但最终可能会在工具链中造成麻烦。如果涉及注解处理和缓存,则更是如此。

增量注解处理

从 Gradle 4.7 开始,增量编译器也支持增量注解处理。所有注解处理器都需要选择加入此功能,否则它们将触发完全重新编译。

作为用户,您可以在 --info 日志中查看哪些注解处理器正在触发完全重新编译。如果编译任务上配置了自定义 executablejavaHome,增量注解处理将被禁用。

使注解处理器增量化

请先查看增量 Java 编译,因为增量注解处理是基于它的。

Gradle 支持两种常见注解处理器类别的增量编译:“隔离型”和“聚合型”。请查阅以下信息以决定哪种类别适合您的处理器。

然后,您可以使用处理器 META-INF 目录中的文件将处理器注册为增量编译。格式为每行一个处理器,其中包含处理器类的完全限定名及其不区分大小写的类别,用逗号分隔。

示例:注册增量注解处理器

processor/src/main/resources/META-INF/gradle/incremental.annotation.processors
org.gradle.EntityProcessor,isolating
org.gradle.ServiceRegistryProcessor,dynamic

如果您的处理器只能在运行时决定它是否是增量的,您可以在 META-INF 描述符中将其声明为“动态”,并在运行时使用 Processor#getSupportedOptions() 方法返回其真实类型。

示例:动态注册增量注解处理器

processor/src/main/java/org/gradle/ServiceRegistryProcessor.java
@Override
public Set<String> getSupportedOptions() {
    return Collections.singleton("org.gradle.annotation.processing.aggregating");
}

这两种类别都有以下限制

  • 它们只能读取 CLASSRUNTIME 保留注解。

  • 它们只能在用户传递 -parameters 编译器参数时读取参数名。

  • 它们必须使用 Filer API 生成文件。以任何其他方式写入文件将导致后续的静默失败,因为这些文件将无法正确清理。如果您的处理器这样做,它就不能是增量的。

  • 它们不能依赖于编译器特定的 API,例如 com.sun.source.util.Trees。Gradle 会包装处理 API,因此尝试转换为编译器特定类型将会失败。如果您的处理器这样做,它就不能是增量的,除非您有一些回退机制。

  • 如果它们使用 Filer#createResource,则 location 参数必须是 StandardLocation 中的以下值之一:CLASS_OUTPUTSOURCE_OUTPUTNATIVE_HEADER_OUTPUT。任何其他参数都将禁用增量处理。

“隔离型”注解处理器

最快的类别,它们独立查看每个带注解的元素,为其创建生成的文件或验证消息。例如,一个 EntityProcessor 可以为每个用 @Entity 注解的类型创建一个 <TypeName>Repository

示例:隔离型注解处理器

processor/src/main/java/org/gradle/EntityProcessor.java
Set<? extends Element> entities = roundEnv.getElementsAnnotatedWith(entityAnnotation);
for (Element entity : entities) {
    createRepository((TypeElement) entity);
}

“隔离型”处理器有以下附加限制

  • 它们必须根据可从其 AST 获取的信息为带注解的类型做出所有决策(代码生成、验证消息)。这意味着您可以分析类型的超类、方法返回类型、注解等,甚至是传递性的。但是您不能根据 RoundEnvironment 中不相关的元素做出决策。这样做会导致后续的静默失败,因为重新编译的文件太少。如果您的处理器需要根据其他不相关元素的组合做出决策,请将其标记为“聚合型”而不是。

  • 它们必须为使用 Filer API 生成的每个文件提供恰好一个原始元素。如果提供了零个或多个原始元素,Gradle 将重新编译所有源文件。

当源文件重新编译时,Gradle 将重新编译所有从它生成的文件。当源文件被删除时,从它生成的文件也会被删除。

“聚合型”注解处理器

这些处理器可以将多个源文件聚合成一个或多个输出文件或验证消息。例如,一个 ServiceRegistryProcessor 可以创建一个包含一个方法的单个 ServiceRegistry,其中每个方法对应一个用 @Service 注解的类型。

示例:聚合型注解处理器

processor/src/main/java/org/gradle/ServiceRegistryProcessor.java
JavaFileObject serviceRegistry = filer.createSourceFile("ServiceRegistry");
Writer writer = serviceRegistry.openWriter();
writer.write("public class ServiceRegistry {");
for (Element service : roundEnv.getElementsAnnotatedWith(serviceAnnotation)) {
    addServiceCreationMethod(writer, (TypeElement) service);
}
writer.write("}");
writer.close();

Gradle 将始终重新处理(但不重新编译)处理器注册的所有带注解的文件。Gradle 将始终重新编译处理器生成的任何文件。

许多流行的注解处理器支持增量注解处理(参见下表)。请直接向注解处理器项目查询最新信息和文档。
注解处理器 支持版本 详情

不适用

不适用

部分支持。

不适用

不适用

DataBinding

隐藏在功能开关后

Dagger

2.18 功能开关支持,2.24 默认启用

kapt

隐藏在功能开关后

Toothpick

2.0

不适用

Glide

不适用

Android-State

不适用

Parceler

不适用

Dart and Henson

不适用

不适用

不适用

不适用

Requery

不适用

不适用

EclipseLink

不适用

不适用

Immutables

不适用

2.2.0 功能开关支持,2.3.0-alpha02 默认启用

不适用

不适用

DBFlow

不适用

AndServer

不适用

不适用

2.0

不适用

不适用

不适用

隐藏在功能开关后

不适用

编译避免

如果依赖项目以ABI兼容的方式更改(仅其私有 API 已更改),则 Java 编译任务将是最新状态。这意味着如果项目 A 依赖于项目 B,并且 B 中的类以 ABI 兼容的方式更改(通常只更改方法体),则 Gradle 不会重新编译 A

一些不影响公共 API 并被忽略的更改类型

  • 更改方法体

  • 更改注释

  • 添加、删除或更改私有方法、字段或内部类

  • 添加、删除或更改资源

  • 更改类路径中 jar 或目录的名称

  • 重命名参数

由于实现细节对注解处理器很重要,因此必须在注解处理器路径上单独声明它们。Gradle 会忽略编译类路径上的注解处理器。

build.gradle.kts
dependencies {
    // The dagger compiler and its transitive dependencies will only be found on annotation processing classpath
    annotationProcessor("com.google.dagger:dagger-compiler:2.44")

    // And we still need the Dagger library on the compile classpath itself
    implementation("com.google.dagger:dagger:2.44")
}
build.gradle
dependencies {
    // The dagger compiler and its transitive dependencies will only be found on annotation processing classpath
    annotationProcessor 'com.google.dagger:dagger-compiler:2.44'

    // And we still need the Dagger library on the compile classpath itself
    implementation 'com.google.dagger:dagger:2.44'
}

变体感知选择

整个 JVM 插件集利用变体感知解析来处理所使用的依赖项。它们还安装了一组属性兼容性和消除歧义规则,以配置 JVM 生态系统的 Gradle 属性