【Gradle jvm插件系列5】Java插件
文章目录
Java插件为项目添加了Java编译、测试和打包功能。它是许多其他JVM语言Gradle插件的基础。您可以在《构建Java项目》章节中找到Java插件的全面介绍和概述。
如上所示,此插件为使用JVM的项目添加了基本的构建块。根据项目类型,其功能集已被其他插件取代,提供更多功能。您不应直接将其应用于您的项目,而应查看java-library或application插件或支持的替代JVM语言。
使用方法
要使用Java插件,请在构建脚本中包含以下内容:
plugins {
id 'java'
}
任务
Java插件为您的项目添加了一些任务,如下所示:
-
compileJava:JavaCompile
- 依赖项:所有贡献编译路径的任务,包括通过项目依赖关系在类路径上的jar任务
- 使用JDK编译器编译生产Java源文件
-
processResources:ProcessResources
- 将生产资源复制到生产资源目录
-
classes
- 依赖项:compileJava,processResources
- 这是一个聚合任务,只依赖于其他任务。其他插件可以将其他编译任务附加到它上面。
-
compileTestJava:JavaCompile
- 依赖项:classes和所有贡献测试编译路径的任务
- 使用JDK编译器编译测试Java源文件
-
processTestResources:Copy
- 将测试资源复制到测试资源目录
-
testClasses
- 依赖项:compileTestJava,processTestResources
- 这是一个聚合任务,只依赖于其他任务。其他插件可以将其他测试编译任务附加到它上面。
-
jar:Jar
- 依赖项:classes
- 根据主源集中附加到类和资源的内容,组装生成产品JAR文件
-
javadoc:Javadoc
- 依赖项:classes
- 使用Javadoc为生产Java源生成API文档
-
test:Test
- 依赖项:testClasses和生成测试运行时类路径的所有任务
- 使用JUnit或TestNG运行单元测试
-
clean:Delete
- 删除项目构建目录
-
cleanTaskName:Delete
- 删除指定任务创建的文件。例如,cleanJar将删除由jar任务创建的JAR文件,cleanTest将删除由test任务创建的测试结果。
SourceSet任务
对于您添加到项目中的每个源集,Java插件会添加以下任务:
-
compileSourceSetJava:JavaCompile
- 依赖项:贡献给该源集编译路径的所有任务
- 使用JDK编译器编译给定源集的Java源文件
-
processSourceSetResources:Copy
- 将给定源集的资源复制到资源目录
-
sourceSetClasses:Task
- 依赖项:compileSourceSetJava,processSourceSetResources
- 准备给定源集的类和资源以进行打包和执行。某些插件可以为该源集添加其他编译任务。
生命周期任务
Java插件将其一些任务附加到Base插件定义的生命周期任务上(Java插件自动应用Base插件),还添加了一些其他生命周期任务:
-
assemble
- 依赖项:jar
- 聚合任务,组装项目中的所有存档。此任务由Base插件添加。
-
check
- 依赖项:test
- 执行验证任务的聚合任务,例如运行测试。某些插件会向check添加自己的验证任务。如果要在完整构建期间执行自定义的Test任务,还应将它们附加到此生命周期任务上。此任务由Base插件添加。
-
build
- 依赖项:check,assemble
- 执行项目的完整构建的聚合任务。此任务由Base插件添加。
-
buildNeeded
- 依赖项:build以及测试运行时类路径配置中所有依赖项目的buildNeeded任务
- 执行项目及其所有依赖项目的完整构建。
-
buildDependents
- 依赖项:build以及测试运行时类路径配置中所有将该项目作为依赖项的项目的buildDependents任务
- 执行项目及所有依赖于它的项目的完整构建。
-
buildConfigName(任务规则)
- 依赖项:生成附加到指定配置名称的制品的所有生成该制品的任务
- 组装指定配置的制品。此规则由Base插件添加。
下图显示了这些任务之间的关系。
项目布局
Java插件假设以下项目布局。这些目录不需要存在或包含任何内容。Java插件会编译它找到的任何东西,并处理缺失的部分。
- src/main/java:生产Java源代码。
- src/main/resources:生产资源,如XML和属性文件。
- src/test/java:测试Java源代码。
- src/test/resources:测试资源。
更改项目布局
您可以通过配置适当的源集来配置项目布局。在后面的章节中将对此进行更详细的讨论。下面是一个简短的示例,演示如何更改主Java和资源源目录。
sourceSets {
main {
java {
srcDirs = ['src/java']
}
resources {
srcDirs = ['src/resources']
}
}
}
源集
该插件添加了以下源集:
- main:包含项目的生产源代码,将其编译并组装为JAR文件。
- test:包含测试源代码,使用JUnit或TestNG进行编译和执行。这通常是单元测试,但您可以在此源集中包含任何测试,只要它们都共享相同的编译和运行时类路径。
源集属性
下表列出了源集的一些重要属性。您可以在SourceSet的API文档中找到更多详细信息。
- name(只读):源集的名称,用于标识它。
- output(只读):源集的输出文件,包含其编译的类和资源。
- output.classesDirs(只读):生成该源集类的目录。
- output.resourcesDir:生成该源集资源的目录。
- compileClasspath:编译源集时使用的类路径。
- annotationProcessorPath:编译源集时使用的处理器路径。
- runtimeClasspath:运行源集类时使用的类路径。
- java(只读):该源集的Java源文件。
- java.srcDirs:包含该源集的Java源文件的源目录。
- java.destinationDirectory:生成编译后Java源文件的目录。
- resources(只读):该源集的资源。
- resources.srcDirs:包含该源集的资源的目录。
- allJava(只读):该源集的所有Java文件。
- allSource(只读):该源集的所有源文件。
定义新的源集
请参阅《Java和JVM项目中的测试》章节中的集成测试示例。
其他简单的源集示例
将源集的类组装为JAR文件:
tasks.register('intTestJar', Jar) {
from sourceSets.intTest.output
}
为源集生成Javadoc:
tasks.register('intTestJavadoc', Javadoc) {
source sourceSets.intTest.allJava
classpath = sourceSets.intTest.compileClasspath
}
在源集中运行测试套件:
tasks.register('intTest', Test) {
testClassesDirs = sourceSets.intTest.output.classesDirs
classpath = sourceSets.intTest.runtimeClasspath
}
依赖管理
Java插件向您的项目添加了一些依赖配置,如下所示。然后,compileJava和test等任务使用其中一个或多个配置来获取相应的文件并使用它们,例如将它们放在编译或运行时类路径上。
依赖配置
有关默认和archives配置的信息,请参阅Base插件参考文档。
有关api或compileOnlyApi配置的信息,请参阅Java Library插件参考文档和Java项目的依赖管理。
- implementation:仅实现依赖项。
- compileOnly:仅编译时依赖项,不在运行时使用。
- compileClasspath:扩展compileOnly和implementation,编译类路径,用于编译源代码。被compileJava任务使用。
- annotationProcessor:编译期间使用的注解处理器。
- runtimeOnly:仅运行时依赖项。
- runtimeClasspath:扩展runtimeOnly和implementation,运行时类路径,包含实现和仅运行时元素。
- testImplementation:测试使用的仅实现依赖项。
- testCompileOnly:仅用于编译测试的其他依赖项,不在运行时使用。
- testCompileClasspath:扩展testCompileOnly和testImplementation,测试编译类路径,用于编译测试源代码。被compileTestJava任务使用。
- testRuntimeOnly:运行测试时的仅运行时依赖项。
- testRuntimeClasspath:扩展testRuntimeOnly和testImplementation,用于运行测试的运行时类路径。被test任务使用。
下图分别显示了主要和测试源集的依赖配置。您可以使用此图例解释颜色:
- 绿色背景:您可以根据该配置声明依赖关系。
- 蓝灰色背景:该配置供任务使用,而不是您声明依赖关系。
- 带等宽字体的浅蓝色背景:一个任务。
对于您添加到项目中的每个源集,Java插件都会添加以下依赖配置:
SourceSet 依赖配置
- sourceSetImplementation:给定源集的编译时依赖项。被sourceSetCompileClasspath和sourceSetRuntimeClasspath使用。
- sourceSetCompileOnly:给定源集的仅编译时依赖项,不在运行时使用。
- sourceSetCompileClasspath:扩展sourceSetCompileOnly和sourceSetImplementation,编译类路径,用于编译源代码。被compileSourceSetJava任务使用。
- sourceSetAnnotationProcessor:给定源集编译期间使用的注解处理器。
- sourceSetRuntimeOnly:给定源集的仅运行时依赖项。
- sourceSetRuntimeClasspath:扩展sourceSetRuntimeOnly和sourceSetImplementation,运行时类路径,包含实现和仅运行时元素。
贡献的扩展
Java插件向项目添加了java扩展。这允许在一个专用的DSL块中配置与Java相关的属性。
以下是java扩展中可用的属性和DSL函数的列表及其简要说明。
工具链和兼容性
- toolchain:JVM工具使用的Java工具链,默认值为build JVM工具链。
- sourceCompatibility:编译Java源码时使用的Java版本兼容性,默认值为此扩展中的工具链的语言版本。
- targetCompatibility:生成类的Java版本,默认值为sourceCompatibility。
打包
- withJavadocJar():自动打包Javadoc并创建带有-artifact的变体javadocElements,该artifact将成为发布的一部分。
- withSourcesJar():自动打包源代码并创建带有-artifact的变体sourceElements,该artifact将成为发布的一部分。
目录属性
- reporting.baseDir:生成报告的目录名称,默认值为reports。
- reportsDir:生成报告的目录,默认值为buildDir/reporting.baseDir。
- testResultsDirName:生成测试结果.xml文件的目录名称,默认值为test-results。
- testResultsDir:生成测试结果.xml文件的目录,默认值为buildDir/testResultsDirName。
- testReportDirName:生成测试报告的目录名称,默认值为tests。
- testReportDir:生成测试报告的目录,默认值为reportsDir/testReportDirName。
- libsDirName:生成库的目录名称,默认值为libs。
- libsDir:生成库的目录,默认值为buildDir/libsDirName。
- distsDirName:生成分发的目录名称,默认值为distributions。
- distsDir:生成分发的目录,默认值为buildDir/distsDirName。
- docsDirName:生成文档的目录名称,默认值为docs。
- docsDir:生成文档的目录,默认值为buildDir/docsDirName。
- dependencyCacheDirName:缓存源依赖信息的目录名称,默认值为dependency-cache。
其他属性
- sourceSets:项目的源集。
- archivesBaseName:用于归档(如JAR或ZIP文件)的基本名称,默认值为projectName。
- manifest:包含在所有JAR文件中的清单,默认为空清单。
Convention属性(已弃用)
Java插件向项目添加了一些Convention属性。您可以在构建脚本中将这些属性用作项目对象的属性。这些属性已弃用,由上面描述的扩展取代。有关它们的信息,请参阅JavaPluginConvention DSL文档。
测试
有关更多详细信息,请参阅《Java和JVM项目中的测试》章节。
发布
components.java:用于发布由jar任务创建的生产JAR的SoftwareComponent。该组件包括JAR的运行时依赖信息。
还请参阅java扩展。
增量Java编译
Gradle自带了一个先进的增量Java编译器,默认情况下处于活动状态。
这带来了以下好处:
- 增量构建速度更快。
- 更改的类尽可能少。不需要重新编译的类保持在输出目录中不变。当使用JRebel时,这真的非常有用——输出类越少,JVM刷新类的速度就越快。
为了帮助您理解增量编译的工作原理,以下是一个高级概述:
- Gradle将重新编译受影响的所有类。
- 如果类已更改或依赖于其他受影响的类,则认为该类受到影响。这适用于在同一项目、另一个项目甚至外部库中定义的其他类。
- 类的依赖关系是通过其字节码中的类型引用或通过编译器插件的符号分析确定的。
- 由于源代码保留的注解在字节码中不可见,对源代码保留的注解的更改将导致完全重新编译。
- 您可以通过应用松耦合的良好软件设计原则来改进增量编译性能。例如,如果在具体类和其依赖项之间放置接口,则仅在接口更改时重新编译依赖类,而在实现更改时不重新编译。
类分析被缓存在项目目录中,因此在干净的检出之后的第一次构建可能会较慢。考虑在构建服务器上关闭增量编译器。
类分析也是存储在构建缓存中的输出,这意味着如果从构建缓存获取编译输出,则增量编译分析也将被获取,并且下一个编译将是增量的。
已知问题:
- 如果使用读取资源(例如配置文件)的注解处理器,需要将这些资源声明为编译任务的输入。
- 如果更改了资源文件,则Gradle将触发完全重新编译。
- 使用自定义可执行文件或javaHome会停用某些优化。如果可能,请使用工具链而不是兼容性设置。
- 如果源结构与包名称不匹配,尽管编译合法,但可能会在工具链中引起麻烦。如果还涉及注解处理和缓存,情况可能更加复杂。
增量注解处理
从Gradle 4.7开始,增量编译器也支持增量注解处理。所有的注解处理器都需要选择此功能,否则它们将触发完全重新编译。
作为用户,您可以在–info日志中查看哪些注解处理器触发了完全重新编译。如果在编译任务上配置了自定义的可执行文件或javaHome,则会禁用增量注解处理。
将注解处理器变为增量处理
请先查看增量Java编译,因为增量注解处理是在其基础上构建的。
Gradle支持两种常见类型的注解处理器进行增量编译:「隔离型」和「聚合型」。请参考下面的信息来确定您的处理器属于哪个类型。
您可以使用处理器的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");
}
这两种类型都有以下限制:
- 只能读取CLASS或RUNTIME保留的注解。
- 只有当用户传递了-parameters编译器参数时,才能读取参数名称。
- 必须使用Filer API来生成文件。以其他方式写入文件将导致后续的静默失败,因为这些文件将无法正确清理。如果您的处理器这样做了,它就无法进行增量处理。
- 不能依赖于编译器特定的API,如com.sun.source.util.Trees。Gradle封装了处理API,因此试图强制转换为编译器特定类型将失败。如果您的处理器这样做了,除非有一些回退机制,否则它无法进行增量处理。
- 如果使用Filer#createResource,location参数必须是StandardLocation中的以下值之一:CLASS_OUTPUT、SOURCE_OUTPUT或NATIVE_HEADER_OUTPUT。其他任何参数都将禁用增量处理。
「隔离型」注解处理器
这是最快的类型,它们独立地查看每个带注解的元素,并为其创建生成的文件或验证消息。例如,一个EntityProcessor可以为每个使用@Entity注解的类型创建一个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可以为每个使用@Service注解的类型创建一个具有一个方法的单个ServiceRegistry。
示例:一个聚合型注解处理器
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将始终重新编译处理器生成的任何文件。
以下是一些常用的注解处理器及其对增量注解处理的支持情况:
注解处理器 | 支持版本 | 详情 |
---|---|---|
Auto Value | 1.6.3 | 无 |
Auto Service | 1.6.3 | 无 |
Auto Value extensions | 部分支持 | 详见问题链接 |
Butterknife | 10.2.0 | 无 |
Lombok | 1.16.22 | 无 |
DataBinding | AGP 3.5.0-alpha5 | 隐藏在特性切换后面 |
Dagger | 2.18 | 2.18 特性切换支持,2.24 默认启用 |
kapt | 1.3.30 | 隐藏在特性切换后面 |
Toothpick | 2.0 | 无 |
Glide | 4.9.0 | 无 |
Android-State | 1.3.0 | 无 |
Parceler | 1.1.11 | 无 |
Dart and Henson | 3.1.0 | 无 |
MapStruct | 1.4.0.Beta1 | 无 |
Assisted Inject | 0.5.0 | 无 |
Realm | 5.11.0 | 无 |
Requery | 未知 | 无 |
EventBus | 3.2.0 | 无 |
EclipseLink | 未知 | 无 |
PermissionsDispatcher | 4.2.0 | 无 |
Immutables | 未知 | 无 |
Room | 2.2.0 | 2.2.0 特性切换支持,2.3.0-alpha02 默认启用 |
Lifecycle | 2.2.0-alpha02 | 无 |
AndroidAnnotations | 4.7.0 | 无 |
DBFlow | 未知 | 无 |
AndServer | 未知 | 无 |
Litho | 0.25.0 | 无 |
Moxy | 2.0 | 无 |
Epoxy | 4.0.0-beta1 | 无 |
JPA Static Metamodel Generator | 5.4.11 | 无 |
DeepLinkDispatch | 5.0.0-beta01 | 隐藏在特性切换后面 |
Shortbread | 1.1.0 | 无 |
编译避免
如果一个依赖项目以ABI兼容的方式进行了更改(仅其私有API发生变化),那么Java编译任务将保持最新状态。这意味着如果项目A依赖于项目B,并且B中的某个类以ABI兼容的方式进行了更改(通常是仅更改方法体),那么Gradle不会重新编译项目A。
以下是不影响公共API且被忽略的一些更改类型:
- 更改方法体
- 更改注释
- 添加、删除或更改私有方法、字段或内部类
- 添加、删除或更改资源
- 更改类路径中JAR文件或目录的名称
- 重命名参数
由于注解处理器对于实现细节非常重要,它们必须在注解处理器路径上单独声明。Gradle会忽略在编译类路径上的注解处理器。
示例:声明注解处理器
dependencies {
// Dagger编译器及其传递依赖项只能在注解处理器路径上找到
annotationProcessor 'com.google.dagger:dagger-compiler:2.44'
// 同时需要在编译类路径上引入Dagger库本身
implementation 'com.google.dagger:dagger:2.44'
}
变种感知选择
那么Gradle不会重新编译项目A。
以下是不影响公共API且被忽略的一些更改类型:
- 更改方法体
- 更改注释
- 添加、删除或更改私有方法、字段或内部类
- 添加、删除或更改资源
- 更改类路径中JAR文件或目录的名称
- 重命名参数
由于注解处理器对于实现细节非常重要,它们必须在注解处理器路径上单独声明。Gradle会忽略在编译类路径上的注解处理器。
示例:声明注解处理器
dependencies {
// Dagger编译器及其传递依赖项只能在注解处理器路径上找到
annotationProcessor 'com.google.dagger:dagger-compiler:2.44'
// 同时需要在编译类路径上引入Dagger库本身
implementation 'com.google.dagger:dagger:2.44'
}
变种感知选择
整个JVM插件集合都利用Variant感知解析来处理所使用的依赖项。它们还安装了一组属性兼容性和消歧规则,以配置适用于JVM生态系统的Gradle属性。