文章目录
- 写在前面
- 1、编译时注解原理
- 2、AbstractProcessor 解析
- 2.1、RoundEnvironment
- 2.2、获取Messager、JavacTrees、TreeMaker、Names、elementUtils
- 2.3、JCTree
- 2.3.1、pos和defs
- 2.3.2、JCTree访问者模式
- 2.4、TreeMaker
- 3、实例一:@NoArgsConstructor 添加无参构造函数
- 3.1、pom
- 3.2、代码
- 4、实例二:@AllArgsConstructor 添加全参构造函数
- 4.1、pom
- 4.2、代码
写在前面
接上一篇文章:java AbstractProcessor 编译时注解(入手体验)
1、编译时注解原理
在java文件编译成class文件之前,对java文件进行操作,生成代码。
AbstractProcessor 完成的就是对java文件进行操作的相关功能。
相关知识点:
1、java文件操作相关的两个类: JCTree 树节点、TreeMaker 树节点构建器
2、JCTree 的一个子类就是java语法中的一个节点,类、方法、字段等这些都被封装成了一个JCTree子类。
3、TreeMaker 里的一个方法用于构建JCTree某个子类,例:treeMaker.MethodDef(…)返回值是JCTree的子类JCMethodDecl(方法节点)
2、AbstractProcessor 解析
一个编译时注解,最后对java文件进行的操作,都在 AbstractProcessor 子类中编写。
- @SupportedAnnotationTypes(“cc.HelloWorld”) ,“cc.HelloWorld” 是HelloWorld注解的类全名。
- @SupportedSourceVersion(SourceVersion.RELEASE_7) ,annotation支持版本,向前兼容到JDK7。
- @AutoService(Processor.class),让AbstractProcessor子类被编译器发现。
- annotations 传进来的注解列表。
- roundEnv 环境,可以从它获取被注解标记的元素,然后获取元素的JCTree对象。
- process 方法返回值,涉及到编译器的处理逻辑,返回true表示已经处理,返回false表示未被处理。
注意AbstractProcessor每一次有修改,就要重新编译该类。或者有修改就删除target,让编译器重新生成class文件
2.1、RoundEnvironment
process 方法的第二个参数RoundEnvironment roundEnv,可以从里面获取java文件的树结构,并往里面加入代码。
RoundEnvironment 的方法:
方法名 | 说明 |
errorRaised() | 如果在前一轮处理中引发错误,则返回true ; 否则返回false 。 |
processingOver() | 如果此轮生成的类型不受后续轮注释处理的影响,则返回true ; 否则返回false 。 |
getRootElements() | 返回前一轮生成的注释处理的root elements 。 |
getElementsAnnotatedWith() | 返回被某个注解标记的元素。 |
getElementsAnnotatedWithAny() | 返回多个注解标记的元素。 |
2.2、获取Messager、JavacTrees、TreeMaker、Names、elementUtils
这些对象中,JavacTrees和TreeMaker是一定会用到的
@SupportedAnnotationTypes("这里填注解类的全名")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
Messager messager; // 用于在编译器打印消息的组件
Names names; // 用于创建标识符的对象
JavacTrees trees; // 语法树
JavacElements elementUtils; // trees 和 elementUtils都可以获取 元素的JCTree对象
TreeMaker treeMaker; // 用来构造语法树节点
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Context context = ((JavacProcessingEnvironment) this.processingEnv).getContext();
this.elementUtils = (JavacElements) this.processingEnv.getElementUtils();
this.messager = this.processingEnv.getMessager();
this.names = Names.instance(this.context);
this.trees = JavacTrees.instance(this.processingEnv);
this.treeMaker = TreeMaker.instance(this.context);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
...
return true;
}
这里有个坑,获取Messager、JavacTrees、TreeMaker这些对象只能在init()方法里面或者init()方法执行之后,否则获取不到这些对象,如下错误示例。
2.3、JCTree
JCTree 的一个子类就是java语法中的一个节点,类、方法、字段等这些都被封装成了一个JCTree子类。
JCTree 从JavacTrees中获取,如下例:
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elementSet = roundEnv.getElementsAnnotatedWith(HelloWorld.class);
elementSet.forEach(element -> {
//获取元素的JCTree对象
JCTree jcTree = trees.getTree(element); // JCTree也可以使用elementUtils获取: JCTree jcTree = elementUtils.getTree(element);
...
});
return true;
}
JCTree 是语法树的基类,包含子类:
父类 | 子类 |
JCTree | 声明节点 JCStatement、方法定义节点 JCMethodDecl、访问标志节点 JCModifiers、表达式节点 JCExpression |
JCStatement 声明节点 | 语块JCBlock、返回语句JCReturn、类定义JCClassDecl、 字段定义JCVariableDecl |
JCExpression 表达式节点 | 赋值语句语JCAssign、变量,类型,关键字JCIdent |
JCMethodDecl 方法节点 | 无 |
JCModifiers 访问标识节点 | 无 |
2.3.1、pos和defs
jcTree.pos 用于指明当前节点在语法树中的位置,treeMaker也有这个字段。
jcTree.defs 类定义的详细语句,包括字段,方法定义等,treeMaker也有这个字段。
2.3.2、JCTree访问者模式
如果你对访问者模式没有了解,建议先阅读这篇文章:访问者模式一篇就够了
JCTree jcTree = trees.getTree(element);
// JCTree利用的是访问者模式,将数据与数据的处理进行解耦,TreeTranslator是访问者,这里可以重写访问类时的逻辑
jcTree.accept(new TreeTranslator(){...});
2.4、TreeMaker
TreeMaker 里的一个方法用于构建JCTree某个子类,例:treeMaker.MethodDef(…)返回值是JCTree的子类JCMethodDecl(方法节点)。
参考官方文档:TreeMaker文档
TreeMaker创建各种节点的方法如下:
方法名 | 说明 |
Modifiers() | 创建访问标志 |
ClassDef() | 类定义 |
MethodDef() | 方法定义 |
VarDef() | 创建字段/变量 |
Ident() | 创建标识符 |
Return() | 返回语句 |
Select() | 创建域访问/方法访问 |
NewClass() | 创建new语句 |
Apply() | 创建方法调用 |
Assign() | 创建赋值语句 |
Exec() | 创建可执行语句 |
Block() | 创建组合语句 |
示例:
// public访问修饰符
JCTree.JCModifiers publicDot = treeMaker.Modifiers(Flags.PUBLIC);
// void关键字
JCTree.JCPrimitiveTypeTree voidDot = treeMaker.TypeIdent(TypeTag.VOID);
// 空语句
JCTree.JCBlock emptyDot = treeMaker.Block(0, List.nil());
// 一个无参构造方法
JCTree.JCMethodDecl noArgsMethod = treeMaker.MethodDef(
publicDot,
names.fromString("<init>"),
voidDot ,
List.nil(),
List.nil(),
List.nil(),
emptyDot,
null);
// this关键字
JCTree.JCIdent thisDot = treeMaker.Ident(names.fromString("this"));
// userPassword (Name类型可以是方法名、字段名、变量名、类名)
Name userPasswordName = names.fromString("userPassword");
// this.userPassword (访问当前方法中的字段userPassword)
JCTree.JCFieldAccess thisUserPassword = treeMaker.Select(thisDot , userPasswordName);
// userPassword 变量
JCTree.JCIdent userPassword = treeMaker.Ident(userPasswordName);
// this.userPassword = userPassword (treeMaker.Apply()以及treeMaker.Assign()需要外面包一层treeMaker.Exec())
treeMaker.Exec(treeMaker.Assign(thisUserPassword, userPassword));
3、实例一:@NoArgsConstructor 添加无参构造函数
本实例未使用JCTree访问者模式,直接添加一个无参构造函数到jcTree.defs中
新建项目a(使用注解)、b(写编译时注解功能)
3.1、pom
项目b的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>b</artifactId>
<groupId>org.example</groupId>
<version>1.0</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- 提供@AutoService注解,让AbstractProcessor子类被编译器发现 -->
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc5</version>
</dependency>
<!-- 提供JCTree等一些功能api -->
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
</project>
项目a的poml.xml(引入项目b)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId>a</artifactId>
<groupId>org.example</groupId>
<version>1.0</version>
<modelVersion>4.0.0</modelVersion>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>b</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
3.2、代码
@NoArgsConstructor
package cc;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface NoArgsConstructor {
}
NoArgsConstructorProcessor.java
package cc;
import com.google.auto.service.AutoService;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Names;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.util.Set;
@SupportedAnnotationTypes("cc.NoArgsConstructor")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class NoArgsConstructorProcessor extends AbstractProcessor {
JavacTrees trees;
TreeMaker treeMaker;
Names names;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Context context = ((JavacProcessingEnvironment) this.processingEnv).getContext();
this.trees = JavacTrees.instance(this.processingEnv);
this.treeMaker = TreeMaker.instance(context);
this.names = Names.instance(context);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elementsSet = roundEnv.getElementsAnnotatedWith(NoArgsConstructor.class);
elementsSet.forEach(element -> {
JCTree.JCClassDecl jcTree = (JCTree.JCClassDecl) trees.getTree(element);
jcTree.defs = jcTree.defs.append(
treeMaker.MethodDef(
treeMaker.Modifiers(Flags.PUBLIC),
names.fromString("<init>"),
treeMaker.TypeIdent(TypeTag.VOID),
List.nil(),
List.nil(),
List.nil(),
treeMaker.Block(0, List.nil()),
null)
);
});
return true;
}
}
项目a中使用@NoArgsConstructor
import cc.NoArgsConstructor;
@NoArgsConstructor
public class Test {
public Test(String s){}
public static void main(String[] args) {}
}
Test编译后的class
4、实例二:@AllArgsConstructor 添加全参构造函数
本实例使用JCTree访问者模式。
新建项目a(使用注解)、b(写编译时注解功能)
4.1、pom
同 3.1、pom
4.2、代码
@AllArgsConstructor
package cc;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface AllArgsConstructor {
}
AllArgsConstructorProcessor.java
package cc;
import com.google.auto.service.AutoService;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Names;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import java.util.Set;
@SupportedAnnotationTypes("cc.AllArgsConstructor")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class AllArgsConstructorProcessor extends AbstractProcessor {
JavacTrees trees;
TreeMaker treeMaker;
Names names;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Context context = ((JavacProcessingEnvironment) this.processingEnv).getContext();
this.trees = JavacTrees.instance(this.processingEnv);
this.treeMaker = TreeMaker.instance(context);
this.names = Names.instance(context);
}
/**
* 字段的语法树节点的集合
*/
private List<JCTree.JCVariableDecl> fieldJcVariables;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(AllArgsConstructor.class);
set.forEach(element -> {
JCTree jcTree = trees.getTree(element);
jcTree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClass) {
before(jcClass);
//添加全参构造方法
jcClass.defs = jcClass.defs.append(
createAllArgsConstructor()
);
after();
}
});
});
return true;
}
/**
* 判断是否是合法的字段
*
* @param jcTree 语法树节点
* @return 是否是合法字段
*/
private static boolean isValidField(JCTree jcTree) {
if (jcTree.getKind().equals(JCTree.Kind.VARIABLE)) {
JCTree.JCVariableDecl jcVariable = (JCTree.JCVariableDecl) jcTree;
Set<Modifier> flagSets = jcVariable.mods.getFlags();
return (!flagSets.contains(Modifier.STATIC)
&& !flagSets.contains(Modifier.FINAL));
}
return false;
}
/**
* 进行一些初始化工作
*
* @param jcClass 类的语法树节点
*/
private void before(JCTree.JCClassDecl jcClass) {
ListBuffer<JCTree.JCVariableDecl> jcVariables = new ListBuffer<>();
//遍历jcClass的所有内部节点,可能是字段,方法等等
for (JCTree jcTree : jcClass.defs) {
//找出所有set方法节点,并添加
if (isValidField(jcTree)) {
//注意这个com.sun.tools.javac.util.List的用法,不支持链式操作,更改后必须赋值
jcVariables.append((JCTree.JCVariableDecl) jcTree);
}
}
this.fieldJcVariables = jcVariables.toList();
}
/**
* 清理
*/
private void after() {
this.fieldJcVariables = null;
}
/**
* 创建全参数构造方法
*
* @return 全参构造方法语法树节点
*/
private JCTree.JCMethodDecl createAllArgsConstructor() {
ListBuffer<JCTree.JCStatement> jcStatements = new ListBuffer<>();
for (JCTree.JCVariableDecl jcVariable : fieldJcVariables) {
Name name = names.fromString(jcVariable.name.toString());
JCTree.JCIdent thisIdent = treeMaker.Ident(names.fromString("this"));
JCTree.JCFieldAccess select = treeMaker.Select(thisIdent, name);
JCTree.JCIdent ident = treeMaker.Ident(names.fromString(jcVariable.name.toString()));
//添加构造方法的赋值语句 " this.xxx = xxx; "
jcStatements.append(
treeMaker.Exec(treeMaker.Assign(select, ident))
);
}
return treeMaker.MethodDef(
treeMaker.Modifiers(Flags.PUBLIC),
names.fromString("<init>"),
treeMaker.TypeIdent(TypeTag.VOID),
List.nil(),
cloneJCVariablesAsParams(treeMaker, fieldJcVariables),
List.nil(),
treeMaker.Block(0 , jcStatements.toList()),
null
);
}
/**
* 克隆一个字段的语法树节点集合,作为方法的参数列表
*
* @param treeMaker 语法树节点构造器
* @param prototypeJCVariables 字段的语法树节点集合
* @return 方法参数的语法树节点集合
*/
static List<JCTree.JCVariableDecl> cloneJCVariablesAsParams(TreeMaker treeMaker, List<JCTree.JCVariableDecl> prototypeJCVariables) {
ListBuffer<JCTree.JCVariableDecl> jcVariables = new ListBuffer<>();
for (JCTree.JCVariableDecl jcVariable : prototypeJCVariables) {
jcVariables.append(cloneJCVariableAsParam(treeMaker, jcVariable));
}
return jcVariables.toList();
}
/**
* 克隆一个字段的语法树节点,该节点作为方法的参数
* 具有位置信息的语法树节点是不能复用的!
*
* @param treeMaker 语法树节点构造器
* @param prototypeJCVariable 字段的语法树节点
* @return 方法参数的语法树节点
*/
static JCTree.JCVariableDecl cloneJCVariableAsParam(TreeMaker treeMaker, JCTree.JCVariableDecl prototypeJCVariable) {
return treeMaker.VarDef(
treeMaker.Modifiers(Flags.PARAMETER), //访问标志。极其坑爹!!!
prototypeJCVariable.name, //名字
prototypeJCVariable.vartype, //类型
null //初始化语句
);
}
}
项目a中使用@AllArgsConstructor
import cc.AllArgsConstructor;
@AllArgsConstructor
public class Test {
private String a;
private String b;
public static void main(String[] args) {}
}
编译后的class文件