jsr269抽象语法树操作API编译期注解处理-简单demo
2021-08-01 本文已影响0人
东南枝下
来自- 《深入理解JVM字节码》
JDK1.6引入了JSR269规范,允许在编译期处理注解,读取、修改、添加抽象语法树中的内容。
lombok插件就是应用了这个
1、自定义注解
package com.jenson.annotation;
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 MyData {
}
2、处理类,继承AbstractProcessor
package com.jenson.annotation;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
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.*;
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("com.jenson.annotation.MyData")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyDataAnnotationProcessor extends AbstractProcessor {
private JavacTrees javacTrees;
private TreeMaker treeMaker;
private Names names;
/**
* 从Context中初始化JavacTrees,TreeMaker,Names
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
javacTrees = JavacTrees.instance(processingEnv);
treeMaker = TreeMaker.instance(context);
names = Names.instance(context);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 获取注解类的集合,之后依次去处理
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(MyData.class);
for (Element element : set) {
// 获取当前类的抽象语法树
JCTree tree = javacTrees.getTree(element);
// 获取抽象语法树的所有节点
// Visitor 抽象内部类,内部定义了访问各种语法节点的方法
tree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
jcClassDecl.defs.stream()
// 过滤,只处理变量类型
.filter(it -> it.getKind().equals(Tree.Kind.VARIABLE))
// 类型强转
.map(it -> (JCTree.JCVariableDecl) it)
.forEach(it -> {
// 添加get方法
jcClassDecl.defs = jcClassDecl.defs.prepend(genGetterMethod(it));
// 添加set方法
jcClassDecl.defs = jcClassDecl.defs.prepend(genSetterMethod(it));
});
super.visitClassDef(jcClassDecl);
}
});
}
return true;
}
private JCTree.JCMethodDecl genGetterMethod(JCTree.JCVariableDecl jcVariableDecl) {
// 生成return语句,return this.xxx
JCTree.JCReturn returnStatement = treeMaker.Return(
treeMaker.Select(
treeMaker.Ident(names.fromString("this")),
jcVariableDecl.getName()
)
);
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<JCTree.JCStatement>().append(returnStatement);
// public 方法访问级别修饰
JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
// 方法名 getXXX ,根据字段名生成首字母大写的get方法
Name getMethodName = createGetMethodName(jcVariableDecl.getName());
// 返回值类型,get类型的返回值类型与字段类型一致
JCTree.JCExpression returnMethodType = jcVariableDecl.vartype;
// 生成方法体
JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
// 泛型参数列表
List<JCTree.JCTypeParameter> methodGenericParamList = List.nil();
// 参数值列表
List<JCTree.JCVariableDecl> parameterList = List.nil();
// 异常抛出列表
List<JCTree.JCExpression> throwCauseList = List.nil();
// 生成方法定义树节点
return treeMaker.MethodDef(
// 方法访问级别修饰符
modifiers,
// get 方法名
getMethodName,
// 返回值类型
returnMethodType,
// 泛型参数列表
methodGenericParamList,
//参数值列表
parameterList,
// 异常抛出列表
throwCauseList,
// 方法默认体
body,
// 默认值
null
);
}
private JCTree.JCMethodDecl genSetterMethod(JCTree.JCVariableDecl jcVariableDecl) {
// this.xxx=xxx
JCTree.JCExpressionStatement statement = treeMaker.Exec(
treeMaker.Assign(
treeMaker.Select(
treeMaker.Ident(names.fromString("this")),
jcVariableDecl.getName()
),
treeMaker.Ident(jcVariableDecl.getName())
)
);
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<JCTree.JCStatement>().append(statement);
// set方法参数
JCTree.JCVariableDecl param = treeMaker.VarDef(
// 访问修饰符
treeMaker.Modifiers(Flags.PARAMETER, List.nil()),
// 变量名
jcVariableDecl.name,
//变量类型
jcVariableDecl.vartype,
// 变量初始值
null
);
// 方法访问修饰符 public
JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
// 方法名(setXxx),根据字段名生成首选字母大写的set方法
Name setMethodName = createSetMethodName(jcVariableDecl.getName());
// 返回值类型void
JCTree.JCExpression returnMethodType = treeMaker.Type(new Type.JCVoidType());
// 生成方法体
JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
// 泛型参数列表
List<JCTree.JCTypeParameter> methodGenericParamList = List.nil();
// 参数值列表
List<JCTree.JCVariableDecl> parameterList = List.of(param);
// 异常抛出列表
List<JCTree.JCExpression> throwCauseList = List.nil();
// 生成方法定义语法树节点
return treeMaker.MethodDef(
// 方法级别访问修饰符
modifiers,
// set 方法名
setMethodName,
// 返回值类型
returnMethodType,
// 泛型参数列表
methodGenericParamList,
// 参数值列表
parameterList,
// 异常抛出列表
throwCauseList,
// 方法体
body,
// 默认值
null
);
}
private Name createGetMethodName(Name variableName) {
String fieldName = variableName.toString();
return names.fromString("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1));
}
private Name createSetMethodName(Name variableName) {
String fieldName = variableName.toString();
return names.fromString("set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1));
}
}
3、定义使用该注解的类
package com.jenson.user;
import com.jenson.annotation.MyData;
@MyData
public class User {
private Long id;
private String name;
}
4、先去掉@MyData注解,编译,查看字节码如下,并没有getter、setter方法
localhost:user Jenson$ javap -c User.class
Compiled from "User.java"
public class com.jenson.user.User {
public com.jenson.user.User();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
}
5、先编译注解类,再编译User.java
# 编译注解包,我这一步报错了,找不到包,估计是ClassPath有问题,用IntellijIDEA编译通过的
javac -cp ./jsr269api/src/main/java/com/jenson/annotation/* -d ./hotchpotch/jsr269api/target/classes/
# 编译User类,这一步没问题
javac -cp ./jsr269api/target/classes -d ./jsr269api/target/classes -processor com.jenson.annotation.MyDataAnnotationProcessor ./jsr269api/src/main/java/com/jenson/user
6、查看字节文件,有了getter、setter方法
localhost:user Jenson$ javap -c User.class
Compiled from "User.java"
public class com.jenson.user.User {
public void setName();
Code:
0: aload_0
1: aload_0
2: getfield #1 // Field name:Ljava/lang/String;
5: putfield #1 // Field name:Ljava/lang/String;
8: return
public java.lang.String getName();
Code:
0: aload_0
1: getfield #1 // Field name:Ljava/lang/String;
4: areturn
public void setId();
Code:
0: aload_0
1: aload_0
2: getfield #2 // Field id:Ljava/lang/Long;
5: putfield #2 // Field id:Ljava/lang/Long;
8: return
public java.lang.Long getId();
Code:
0: aload_0
1: getfield #2 // Field id:Ljava/lang/Long;
4: areturn
public com.jenson.user.User();
Code:
0: aload_0
1: invokespecial #3 // Method java/lang/Object."<init>":()V
4: return
}
7、打成jar包
在MyDataAnnotationProcessor
上加上注解@AutoService(Processor.class),这个注解来自谷歌的一个依赖,可以自动添加META-INF/services/javax.annotation.processing.Processor
文件,就不用手动写了。
pom.xml 如下
...
<dependencies>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc5</version>
</dependency>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.7</version>
<scope>system</scope>
<systemPath>/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/tools.jar</systemPath>
</dependency>
</dependencies>
<build>
<finalName>my-data</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
...
最后打包 mvn package