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

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

sourceSetClasses任务

依赖于: 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

仅编译时依赖项,运行时不使用。

compileClasspath 扩展 compileOnly, implementation

编译类路径,用于编译源代码。由任务 compileJava 使用。

annotationProcessor

编译期间使用的注释处理器。

runtimeOnly

仅运行时依赖项。

runtimeClasspath 扩展 runtimeOnly, implementation

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

testImplementation 扩展 implementation

测试的仅实现依赖项。

testCompileOnly

仅用于编译测试的额外依赖项,运行时不使用。

testCompileClasspath 扩展 testCompileOnly, testImplementation

测试编译类路径,用于编译测试源代码。由任务 compileTestJava 使用。

testRuntimeOnly 扩展 runtimeOnly

运行测试的运行时依赖项。

testRuntimeClasspath 扩展 testRuntimeOnly, testImplementation

运行测试的运行时类路径。由任务 test 使用。

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

  • 绿色背景 - 您可以针对配置声明依赖项。

  • 蓝灰色背景 - 配置用于任务使用,而不是您声明依赖项。

  • 浅蓝色背景,带等宽文本 - 一个任务。

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

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

SourceSet 依赖配置

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

Java 工具链,用于使用 JVM 工具的任务,例如编译和执行。默认值:构建 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

生成测试报告的目录名称,相对于报告目录。默认值: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 支持两种常见类型的注解处理器的增量编译:“隔离”和“聚合”。请参考以下信息来确定哪种类别适合您的处理器。

您可以通过处理器 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 将始终重新编译处理器生成的任何文件。

许多流行的注解处理器支持增量注解处理(见下表)。请直接与注解处理器项目联系,获取最新信息和文档。
注解处理器 支持版本 详情

N/A

N/A

部分支持。

N/A

N/A

DataBinding

隐藏在功能切换后面

Dagger

2.18 功能切换支持,2.24 默认启用

kapt

隐藏在功能切换后面

Toothpick

2.0

N/A

Glide

N/A

Android-State

N/A

Parceler

N/A

Dart 和 Henson

N/A

N/A

N/A

N/A

Requery

N/A

N/A

EclipseLink

N/A

N/A

Immutables

N/A

2.2.0 功能切换支持,2.3.0-alpha02 默认启用

N/A

N/A

DBFlow

N/A

AndServer

N/A

N/A

2.0

N/A

N/A

N/A

隐藏在功能切换后面

N/A

编译避免

如果依赖项目以 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 插件都利用 变体感知解析 来处理所使用的依赖项。它们还安装了一组属性兼容性和消除歧义规则,以 配置 Gradle 属性,以适应 JVM 生态系统的具体情况。