1️⃣ AbstractProcessor的含义
AbstractProcessor 就是注解处理器 ,在java文件编译成class文件之前,对java文件进行操作,生成代码。(可以参考lombok)
2️⃣ 相关知识点
📝 1. JCTree语法树(AST)(访问者模式)
-
JCTree 的一个子类就是java语法中的一个节点,类、方法、字段等这些都被封装成了一个JCTree子类。可以安装JDT AstView(idea插件)可以看到每个class的语法树
- JCTree
- JCStatement (声明节点)
- 语句块:
JCBlock
- 返回语句:
JCReturn
- 类定义:
JCClassDecl
- 字段定义:
JCVariableDecl
- 语句块:
- JCExpression (表达式节点)
- 赋值语句:
JCAssign
- 变量、类型、关键字:
JCIdent
- 赋值语句:
- JCMethodDecl (方法节点)
- 无子类
- JCModifiers (访问标识节点)
- 无子类
- JCStatement (声明节点)
📝 2. TreeMaker 语法树构建器
TreeMaker 是 JavaPoet 中的一个重要组件,它用于构建抽象语法树(AST),以生成Java代码。抽象语法树是编程语言中源代码语法结构的树状表示,它将源代码分解为语法单元,并以树的形式表示这些语法单元之间的层次关系。
在 JavaPoet 中,TreeMaker 主要用于创建以下类型的语法树节点:
- 类定义
- 方法定义
- 字段定义
- 构造函数定义
- 接口定义
- 枚举定义
- 注解定义
- 泛型类型参数定义
- 注解参数定义
- 方法参数定义
- 方法调用
- 字段访问
- 赋值表达式
- 返回语句
- 条件语句
- 循环语句
- 异常捕获语句
- 类型转换表达式
- 类型字面量表达式
- 注解表达式
- 数组访问表达式
- 字符串拼接表达式
- 数组初始化表达式
- 等等
TreeMaker 提供了一组方法,用于创建这些不同类型的语法树节点,并设置它们的属性、修饰符、注解等信息。开发者可以使用这些方法来构建所需的语法树,并最终生成Java代码。
例如,要创建一个类定义节点,可以使用以下代码:
TypeSpec classDefinition = TypeSpec.classBuilder("MyClass")
.addModifiers(Modifier.PUBLIC)
.addMethod(MethodSpec.methodBuilder("myMethod")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addStatement("System.out.println(\"Hello, World!\")")
.build())
.build();
在上述代码中,TypeSpec.classBuilder("MyClass")
创建了一个类定义节点,.addModifiers(Modifier.PUBLIC)
添加了 public
修饰符,.addMethod(...)
添加了一个方法定义节点,最终通过 .build()
构建了一个完整的类定义。
TreeMaker 的灵活性和丰富的方法使得可以轻松地生成复杂的Java代码,并与 JavaPoet 的其他功能一起使用,实现自动化的代码生成。这对于注解处理器等场景非常有用,可以根据注解信息生成相应的Java代码。
📝 3. 关于 jcTree.pos
和 treeMaker.pos
的说明
在处理注解时,我们经常需要访问和操作语法树。为了保证操作的准确性和一致性,特别是在生成新的代码元素时,我们需要确保当前节点在语法树中的位置(jcTree.pos
)与语法树构建器(treeMaker
)中的位置是一致的。
/**
* @Description 此行注释是必需的。如果缺少此行,在添加 setter 时可能会出现错误。
* 在处理下一个使用目标注解的地方之前,我们需要首先设置 `pos`。系统中有很多类或方法使用了目标注解,
* 但在代码生成过程中(即在 `accept` 方法中重写的 `visitClassDef` 为当前类添加或删除元素时),
* 没有修改 `treeMaker.pos`,这导致即使操作已经移动到下一个类文件,`pos` 仍然保持不变。
* 实际上,当我在 `visitClassDef` 中将添加或删除操作注释掉时,这个错误不会出现。
**/
treeMaker.pos = tree.pos;
解释:
jcTree.pos
:用于指示当前节点在语法树中的位置。treeMaker.pos
:在语法树构建器中,表示当前节点的位置。jcTree.defs
:表示类的详细定义,包括字段、方法定义等。treeMaker.defs
:在语法树构建器中,也表示类的详细定义。
为了保证代码的正确生成和完整性,我们需要确保这些位置和定义在操作时始终保持同步。
3️⃣ AbstractProcessor 解析
📝 1. 对象初始化说明
在进行注解处理时,有几个核心的工具对象,它们通常需要在 init
方法中进行初始化。这些工具对象有助于我们处理注解、生成代码、操作语法树等。下面是这些工具对象的简要说明以及它们通常如何在 init
方法中进行初始化:
-
Messager: 它提供了一种方式来报告错误、警告以及其他通知。
-
JavacTrees: 用于获取语法树(
JCtree
)实例。 -
TreeMaker: 它是一个工厂类,用于创建语法树节点。
-
Names: 用于创建名称(name)实例。
-
elementUtils: 提供了各种实用方法来操作元素。
以下是如何在 init
方法中初始化这些对象的示例:
private Messager messager;
private JavacTrees trees;
private TreeMaker treeMaker;
private Names names;
private Elements elementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
trees = JavacTrees.instance(processingEnv);
treeMaker = TreeMaker.instance(trees.getContext());
names = Names.instance(trees.getContext());
elementUtils = processingEnv.getElementUtils();
}
📝 2. 注解处理器详解
注解处理器是一个非常强大的工具,它允许开发者在编译时对注解进行处理,进行代码生成或其他操作。下面我们对上述内容进行详细解析:
-
@SupportedAnnotationTypes(“cc.HelloWorld”):
- 描述:这个注解定义了注解处理器是为哪些注解设计的。
"cc.HelloWorld"
是HelloWorld
注解的完整类名。 - 作用:告诉编译器这个处理器是用来处理
cc.HelloWorld
这个注解的。
- 描述:这个注解定义了注解处理器是为哪些注解设计的。
-
@SupportedSourceVersion(SourceVersion.RELEASE_7):
- 描述:这个注解指定了注解处理器支持的Java版本。在这里,它被设置为 JDK 7。
- 作用:确保处理器只处理指定版本的Java代码。
-
@AutoService(Processor.class):
- 描述:这是一个注册服务的注解,使得
AbstractProcessor
的子类能够被编译器自动发现。 - 作用:自动为注解处理器生成
META-INF/services/javax.annotation.processing.Processor
文件,这是Java服务提供者接口(SPI)的一部分。
- 描述:这是一个注册服务的注解,使得
-
annotations:
- 描述:这是传递给
process
方法的注解列表。 - 作用:告诉处理器这一轮应该处理哪些注解。
- 描述:这是传递给
-
roundEnv:
- 描述:它提供了当前处理轮次的环境信息。
- 作用:可以从中获取被注解标记的元素,然后进一步获取元素的
JCTree
对象,进行代码生成或其他操作。
-
process 方法的返回值:
- 描述:这个返回值告诉编译器这个注解是否已经被这个处理器处理。
- 作用:如果返回
true
,则这些注解不会再被其他处理器处理;如果返回false
,则这些注解可能会被其他处理器处理。
注意:每当修改了
AbstractProcessor
,为确保所有更改都生效,需要重新编译该类。在Maven项目中,可以先执行clean
命令,然后再执行compile
命令。如果正在使用IDE进行调试,确保在调试之前先进行clean
操作。
4️⃣ Javapoet(开源框架用于操作语法树)
常用类和方法是与 JavaPoet 这个库相关的,它是一个用于生成Java代码的工具库,通常用于注解处理器中。下面是常用类和方法的简要解释:
-
MethodSpec:代表一个构造函数或方法的声明。你可以使用它来创建方法或构造函数的定义,包括参数、注解、Javadoc等信息。
-
TypeSpec:代表一个类、接口或枚举的声明。使用它可以创建类、接口或枚举的定义,包括成员变量、方法、内部类等信息。
-
FieldSpec:代表一个成员变量或字段的声明。它用于创建成员变量的定义,包括类型、名称、修饰符等信息。
-
JavaFile:包含一个顶级类的Java文件。你可以将生成的类添加到Java文件中,并最终输出为一个Java源文件。
常用方法和占位符:
-
MethodSpec.addJavadoc("XXX")
:可以在方法上方添加注释。 -
MethodSpec.constructorBuilder()
:用于创建构造函数的构造器。 -
MethodSpec.addAnnotation(Override.class)
:用于在方法上添加注解。 -
TypeSpec.enumBuilder("XXX")
:用于生成一个枚举类型的定义。 -
TypeSpec.interfaceBuilder("HelloWorld")
:用于生成一个接口类型的定义。
占位符用于在生成代码时插入字符串、类型或变量名等内容,例如,$S
用于插入字符串,$T
用于插入类型,$N
用于插入变量名等。
这些类和方法可以帮助开发者通过代码生成方式来创建Java类、方法、字段等,从而实现自动化的代码生成和处理。在注解处理器中,它们通常与注解处理器的逻辑一起使用,用于生成与注解相关的代码。