JSR 269是jdk1.6中引入的,在此之前,开发者只能在运行期对注解进行处理,而JSR 269允许开发这在编译期对注解进行处理,以此可以完成很多java不支持的特性,甚至创造新的语法糖。大名鼎鼎的lombok就是基于JSR 269来实现的。
1. 原理介绍
javac的编译过程如下图所示:
可以看到JSR 269,也就是注解处理在编译产生抽象语法树AST之后,在此处插入一些逻辑,并且可以对AST进行修改。因为JSR 269注解处理是在attr(语义分析,各种静态检查都是在这里做的)之前的,所以我们甚至可以将一颗不符合java语义的AST处理成符合意义的AST,从而完成一些cool operation。
jdk提供了一套操作抽象语法树的API,有三个核心类
-
Names类提供访问AST中标识符的方法。
-
例如访问this标识符,name.fromString("this")
-
JCTree是代表各种AST节点的基类,其有很多子类,如JCStatement,JCExpression,JCMethodDecl,JCModifier等。
-
JCStatement是用来代表java声明语句的,它也有很多子类,如JCReturn,JCBlock(AST中的代码块节点,对应到java中就是中括号包含的一段代码),JCClassDecl(AST中的类声明节点),JCVariableDecl(AST中的变量声明节点),JCTry,JCIf,JCFroLoop(AST中的循环语句节点)等。
-
JCExpression用来代表表达式语句节点,常用的子类有JCAssign(赋值语句,如x=1),JCIdent(标识符语句),JCBinary(二元运算语句,如1+2),JCLiteral(常量语句)。
-
JCMethodDecl用于代表方法定义
-
-
JCModifiers代表AST访问标记节点,其有一个flag属性,取值如下:
-
TreeMaker封装了操作AST节点的方法。其拥有丰富的创建各种AST的方法,常用的有
-
Binary,生成二元表达式节点,如1+2:
treeMaker.Binary(JCTree.Tag.PLUS, treeMaker.Literal(1), treeMaker.Literal(2));
-
Ident,用于创建标识符节点,如获取标识符x:
treeMaker.Ident(names.fromString("x"));
-
Select,创建一个字段或方法,或访问某个Field,如this.id对应的Select代码为
treeMaker.Select(treeMaker.Ident(names.fromString("this")), names.fromString("id"));
-
Assign,生成一个赋值语句节点,如x=0:
treeMaker.Assign( treeMaker.Ident(names.fromString("x")), //左表达式 treeMaker.Literal(0) //右表达式 );
-
Exec,用于包装JCAssign对象,使其返回一个JCExpressionStatement,JCExpressionStatement就可以直接用来生成方法AST节点了,如下
treeMaker.Exec( treeMaker.Assign( treeMaker.Ident(names.fromString("x")), //左表达式 treeMaker.Literal(0) //右表达式 ) );
2. 自定义MyData注解实现getter/setter自动生成
下面来模拟lombok的Data注解的实现,来看一下JSR 269应该如何进行实践。
先声明MyData注解。
package com.xycode.techlecture.jsr269;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author: xycode
* @email: lianguang.xy@alibaba-inc.com
* @date: 2022/7/12
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface MyData {
}
接下来创建注解处理器类,jdk中有一个AbstractProcessor,我们只需继承它,然后编写字节的注解处理逻辑,目前是使得标注了MyData注解的类能够自动支持getter、setter方法。如下:
package com.xycode.techlecture.jsr269;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import com.sun.source.tree.Tree.Kind;
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.JCTree.JCClassDecl;
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.ListBuffer;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
/**
* 注解处理器实现(编译期执行), 实现类型lombok的@Data注解功能
*
* @author: xycode
* @email: lianguang.xy@alibaba-inc.com
* @date: 2022/7/12
*/
//SupportedAnnotationTypes用于指定改processor支持的注解
@SupportedAnnotationTypes("com.xycode.techlecture.jsr269.MyData")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyDataAnnotationProcessor extends AbstractProcessor {
/**
* 描述语法树的实例类
*/
private JavacTrees javacTrees;
/**
* 创建语法树节点的工具类
*/
private TreeMaker treeMaker;
/**
* 访问语法树中的标识符
* eg: names.fromString("str")
*/
private Names names;
/**
* 从AST上下文中初始化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);
}
/**
* 生成getter方法节点
*/
private JCTree.JCMethodDecl genGetterMethod(JCTree.JCVariableDecl jcVariableDecl) {
Name variableDeclName = jcVariableDecl.getName();
//生成语句: return this.xxx
JCTree.JCReturn returnStatement = treeMaker.Return(
treeMaker.Select(
treeMaker.Ident(names.fromString("this")), variableDeclName
)
);
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<JCTree.JCStatement>().append(returnStatement);
//生成public修饰符
JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
//拼接方法名 getXxx
String getMethodNameStr = "get" + variableDeclName.toString().substring(0, 1).toUpperCase()
+ variableDeclName.toString().substring(1);
Name getMethodName = names.fromString(getMethodNameStr);
//生成返回类型标识
JCTree.JCExpression returnMethodType = jcVariableDecl.vartype;
//生成方法体
JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
//生成泛型参数列表
com.sun.tools.javac.util.List<JCTree.JCTypeParameter> methodGenericParameterList
= com.sun.tools.javac.util.List.nil();
//生成参数值列表
com.sun.tools.javac.util.List<JCTree.JCVariableDecl> methodParameterValList
= com.sun.tools.javac.util.List.nil();
//生成抛出的异常列表
com.sun.tools.javac.util.List<JCTree.JCExpression> throwExceptionList = com.sun.tools.javac.util.List.nil();
//生成方法定义AST节点
return treeMaker.MethodDef(modifiers, //public
getMethodName, //方法名: getXxx
returnMethodType, //返回类型
methodGenericParameterList, //泛型参数列表
methodParameterValList, //参数值列表
throwExceptionList, //抛出异常列表
body, //方法体
null);
}
/**
* 生成setter方法节点
*/
private JCTree.JCMethodDecl genSetterMethod(JCTree.JCVariableDecl jcVariableDecl) {
Name variableDeclName = jcVariableDecl.getName();
//生成语句: this.xxx = xxx
JCTree.JCExpressionStatement statement = treeMaker.Exec(
treeMaker.Assign(
treeMaker.Select(
treeMaker.Ident(names.fromString("this")), variableDeclName
),//左表达式部分: this.xxx
treeMaker.Ident(variableDeclName) //右表达式部分: xxx
)
);
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<JCTree.JCStatement>().append(statement);
//setter方法参数
JCTree.JCVariableDecl param = treeMaker.VarDef(
treeMaker.Modifiers(Flags.PARAMETER, com.sun.tools.javac.util.List.nil()), //访问修饰符
variableDeclName, //变量名字
jcVariableDecl.vartype, //变量类型
null //变量初始值
);
//生成public修饰符
JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
//拼接方法名 getXxx
String getMethodNameStr = "set" + variableDeclName.toString().substring(0, 1).toUpperCase()
+ variableDeclName.toString().substring(1);
Name getMethodName = names.fromString(getMethodNameStr);
//生成返回类型标识 void
JCTree.JCExpression returnMethodType = treeMaker.Type(new Type.JCVoidType());
//生成方法体
JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
//生成泛型参数列表
com.sun.tools.javac.util.List<JCTree.JCTypeParameter> methodGenericParameterList
= com.sun.tools.javac.util.List.nil();
//生成参数值列表
com.sun.tools.javac.util.List<JCTree.JCVariableDecl> methodParameterList = com.sun.tools.javac.util.List.of(
param);
//生成抛出的异常列表
com.sun.tools.javac.util.List<JCTree.JCExpression> throwExceptionList = com.sun.tools.javac.util.List.nil();
//生成方法定义AST节点
return treeMaker.MethodDef(modifiers, //public
getMethodName, // 方法名: setXxx
returnMethodType, //返回类型
methodGenericParameterList, //泛型参数列表
methodParameterList, //参数类型列表
throwExceptionList, //抛出异常列表
body, //方法体
null);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//获取标注了MyData注解的元素, 这里实际上只有类元素
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(MyData.class);
for (Element element : set) {
//获取标注了MyData注解的类的语法树
JCTree tree = javacTrees.getTree(element);
//notice: 解决编译错误【java.lang.AssertionError: Value of x -1】
// 因为treeMaker.pos的值是不会变的(=-1), 所以在遍历是需要实时更新
treeMaker.pos = tree.pos;
//遍历语法树(在遇到visitClassDef事件, 也就是访问到类定义节点时去修改语法树节点)
tree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCClassDecl jcClassDecl) {
//获取定义在该类下的所有元素(成员变量, 方法等)
jcClassDecl.defs.stream()
//过滤出变量类型的元素
.filter(o -> o.getKind().equals(Kind.VARIABLE))
//强制转换元素为变量类型元素
.map(o -> ((JCTree.JCVariableDecl)o))
//遍历处理每个变量元素
.forEach(o -> {
//为类定义节点新增getter方法
jcClassDecl.defs = jcClassDecl.defs.prepend(genGetterMethod(o));
//为类定义节点新增setter方法
jcClassDecl.defs = jcClassDecl.defs.prepend(genSetterMethod(o));
});
//修改类节点完毕
super.visitClassDef(jcClassDecl);
}
});
}
return true;
}
}
接下来声明一个User类,如下:
package com.xycode.techlecture.jsr269;
/**
* @author: xycode
* @email: lianguang.xy@alibaba-inc.com
* @date: 2022/7/12
*/
@MyData
public class User {
private Integer id;
private String name;
public User() {
}
public static void main(String[] args) {
User user = new User();
user.setId(1234);
user.setName("xycode");
System.out.println(user.getId());
System.out.println(user.getName());
}
}
可以看到User类这里没有创建getter,setter方法,但是main中使用到了,显然正常情况下会编译报错,但是借助JSR 269的插件化注解处理能力,我们可以在编译期间介入AST的生成,从而完成getter,setter的正常调用,编译命令如下:
先编译MyData与MyDataAnnotationProcessor:
再编译User:
可以看出使能够编译成功的,反编译看一下User,如下
可以看出getter,setter方法已经正常生成了,执行如下:
执行也是ok的。