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 插件将其一些任务附加到 Base 插件(Java 插件会自动应用它)定义的生命周期任务,同时还添加了一些其他生命周期任务:

assemble

依赖于jar

聚合任务,组装项目中的所有档案。此任务由 Base 插件添加。

check

依赖于test

聚合任务,执行验证任务,例如运行测试。一些插件会向 check 添加它们自己的验证任务。如果你希望自定义的 Test 任务在完整构建中执行,也应将它们附加到此生命周期任务。此任务由 Base 插件添加。

build

依赖于check, assemble

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

buildNeeded

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

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

buildDependents

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

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

buildConfigName — 任务规则

依赖于:所有生成附加到指定(ConfigName)配置的制品的任务

组装指定配置的制品。此规则由 Base 插件添加。

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

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 配置的信息,请查阅 Base 插件参考文档。

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

依赖声明配置

implementation

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

compileOnly

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

runtimeOnly

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

testImplementation 扩展自 implementation

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

testCompileOnly

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

testRuntimeOnly 扩展自 runtimeOnly

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

annotationProcessor

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

可解析配置

compileClasspath 扩展自 compileOnly, implementation

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

runtimeClasspath 扩展自 runtimeOnly, implementation

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

testCompileClasspath 扩展自 testCompileOnly, testImplementation

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

testRuntimeClasspath 扩展自 testRuntimeOnly, testImplementation

表示运行 test 源时使用的类路径,包含 implementation 的元素以及 runtime only 的元素。由任务 test 使用。

以下图表分别显示了 maintest 源集的依赖配置。你可以使用此图例来解释颜色:

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

  • 绿色背景 — 该配置用于任务消费,而不是供你声明依赖项。

  • 灰色背景 — 一个任务。

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

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

源集依赖配置

sourceSetImplementation

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

sourceSetCompileOnly

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

sourceSetCompileClasspath 扩展自 sourceSetCompileOnly, sourceSetImplementation

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

sourceSetAnnotationProcessor

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

sourceSetRuntimeOnly

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

sourceSetRuntimeClasspath 扩展自 sourceSetRuntimeOnly, sourceSetImplementation

运行时类路径包含 implementation 的元素以及 runtime only 的元素。

贡献的扩展

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 并创建一个变体 javadocElements,其制品为 -javadoc.jar,它将作为发布的一部分。

withSourcesJar()

自动打包源代码并创建一个变体 sourceElements,其制品为 -sources.jar,它将作为发布的一部分。

目录属性

String reporting.baseDir

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

(只读) File reportsDir

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

String testResultsDirName

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

(只读) File testResultsDir

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

String testReportDirName

相对于 reports 目录的测试报告生成目录名称。默认值: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 插件向项目添加了许多约定属性。你可以在构建脚本中像使用项目对象的属性一样使用这些属性。这些属性已弃用,并已被上面描述的扩展取代。有关它们的信息,请参阅 JavaPluginConvention DSL 文档。

测试

有关更多详细信息,请参阅Java 和 JVM 项目中的测试章节。

发布

components.java

一个 SoftwareComponent,用于发布jar 任务创建的生产环境 JAR。此组件包含 JAR 的运行时依赖信息。

另请参阅 java 扩展

增量式 Java 编译

Gradle 带有一个复杂的增量式 Java 编译器,它默认是激活的。

这为您带来以下好处:

  • 增量构建速度快得多。

  • 更改的类文件数量最少。不需要重新编译的类在输出目录中保持不变。一个在这种情况下非常有用的示例是使用 JRebel — 输出类更改得越少,JVM 刷新类的速度就越快。

为了帮助您理解增量编译的工作原理,以下提供一个高层概览:

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

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

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

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

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

  • 类分析缓存在项目目录中,因此首次干净检出后的构建可能会较慢。考虑在构建服务器上关闭增量编译器。

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

已知问题

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

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

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

  • 源文件结构与包名不匹配(虽然编译时合法)最终可能在工具链中引起麻烦。如果涉及注解处理和 缓存,情况则更甚。

增量注解处理

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

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

使注解处理器增量化

请首先查看 增量 Java 编译,因为增量注解处理以此为基础。

Gradle 支持两种常见类型的注解处理器的增量编译:“独立的”(isolating)和“聚合的”(aggregating)。请参考以下信息来决定哪种类别适合您的处理器。

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

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

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

如果您的处理器只能在运行时决定它是否是增量的,您可以将其在 META-INF 描述符中声明为“dynamic”,并在运行时使用 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 编译任务将是最新(up-to-date)的。这意味着如果项目 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 插件利用了 变体感知解析 来处理使用的依赖项。它们还安装了一组属性兼容性和消歧规则,以 配置 Gradle 属性 以适应 JVM 生态系统的具体情况。