在Javac源码中,插入式注解处理器的初始化过程是在initPorcessAnnotations() 方法中完成的,而它的执行过程则是在processAnnotations()方法中完成的,这个方法判断是否还有新的注解处理器需要执行,如果有的话,通过com.sun.tools.javac.processing.JavacProcessingEnvironment类的 **doProcessing()**方法生成一个新的JavaCompiler对象对编译的后续步骤进行处理。
在JDK1.5之后,Java语言提供了对注解(Annotation)的支持,这些注解与普通的Java代码一样,是在运行期间发挥作用的。在**JDK1.6中实现了JSR-269规范,JSR-269:Pluggable Annotations Processing API(插入式注解处理API)。提供了一组插入式注解处理器的标准API在编译期间对注解进行处理。**我们可以把它看做是一组编译器的插件,在这些插件里面,可以读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环称为一个Round,这是一个回环过程。
需求,模拟lombok生成get/set方法。
实现步骤:
编写get/set处理器,该类需要继承AbstractProcessor
编写支持版本,使用注解**@SupportSourceVersion** 或重新方法getSupportedSourceVersion
处理支持的注解,使用注解**@SupportedAnnotationTypes** 或重写方法getSupportedAnnotationTypes
重写init方法
重写process方法
在根目录下创建文件META-INF/services/javax.annotation.processing.Processor可在resources目录下创建,其内容如下
xin.spring.java.processor.GetterProcessor
xin.spring.java.processor.SetterProcessor
在此基础上需要提供两个注解:
package xin.spring.java.processor; 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 Getter { } package xin.spring.java.processor; 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 Setter { }
get编码实现:
package xin.spring.java.processor; 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.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 javax.tools.Diagnostic; import java.util.Set; //对Getter感兴趣 @SupportedAnnotationTypes("xin.spring.java.processor.Getter") //支持的版本,使用1.8就写这个 //@SupportedSourceVersion(SourceVersion.RELEASE_8) public class GetterProcessor extends AbstractProcessor { // 编译时期输入日志的 private Messager messager; // 将Element转换为JCTree的工具,提供了待处理的抽象语法树 private JavacTrees trees; // 封装了创建AST节点的一些方法 private TreeMaker treeMaker; // 提供了创建标识符的方法 private Names names; @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.messager = processingEnv.getMessager(); this.trees = JavacTrees.instance(processingEnv); Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); this.treeMaker = TreeMaker.instance(context); this.names = Names.instance(context); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // 获取被@Getter注解标记的所有元素(这个元素可能是类、变量、方法等等) Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Getter.class); set.forEach(element -> { // 将Element转换为JCTree JCTree jcTree = trees.getTree(element); jcTree.accept(new TreeTranslator() { /*** * JCTree.Visitor有很多方法,我们可以通过重写对应的方法,(从该方法的形参中)来获取到我们想要的信息: * 如: 重写visitClassDef方法, 获取到类的信息; * 重写visitMethodDef方法, 获取到方法的信息; * 重写visitVarDef方法, 获取到变量的信息; * @param jcClassDecl */ @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { //创建一个变量语法树节点的List List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil(); // 遍历defs,即是类定义的详细语句,包括字段、方法的定义等等 for (JCTree tree : jcClassDecl.defs) { if (tree.getKind().equals(Tree.Kind.VARIABLE)) { JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree; jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl); } } // 对于变量进行生成方法的操作 jcVariableDeclList.forEach(jcVariableDecl -> { messager.printMessage(Diagnostic.Kind.NOTE, "get " + jcVariableDecl.getName() + " has been processed"); treeMaker.pos = jcVariableDecl.pos; //类里的前面追加生成的Getter方法 jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl)); }); super.visitClassDef(jcClassDecl); } }); }); //我们有修改过AST,所以返回true return true; } private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) { /*** * JCStatement:声明语法树节点,常见的子类如下 * JCBlock:语句块语法树节点 * JCReturn:return语句语法树节点 * JCClassDecl:类定义语法树节点 * JCVariableDecl:字段/变量定义语法树节点 * JCMethodDecl:方法定义语法树节点 * JCModifiers:访问标志语法树节点 * JCExpression:表达式语法树节点,常见的子类如下 * JCAssign:赋值语句语法树节点 * JCIdent:标识符语法树节点,可以是变量,类型,关键字等等 */ ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()))); JCTree.JCBlock body = treeMaker.Block(0, statements.toList()); return treeMaker.MethodDef( treeMaker.Modifiers(Flags.PUBLIC),//mods:访问标志 getNewMethodName(jcVariableDecl.getName()),//name:方法名 jcVariableDecl.vartype,//restype:返回类型 List.nil(),//typarams:泛型参数列表 List.nil(),//params:参数列表 List.nil(),//thrown:异常声明列表 body,//方法体 null); } private Name getNewMethodName(Name name) { String s = name.toString(); return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length())); } }
set编码实现:
package xin.spring.java.processor; 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 javax.tools.Diagnostic; import java.util.Set; @SupportedAnnotationTypes("xin.spring.java.processor.Setter") //@SupportedSourceVersion(SourceVersion.RELEASE_8) public class SetterProcessor extends AbstractProcessor { private Messager messager; private JavacTrees trees; private TreeMaker treeMaker; private Names names; @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.messager = processingEnv.getMessager(); this.trees = JavacTrees.instance(processingEnv); Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); this.treeMaker = TreeMaker.instance(context); this.names = Names.instance(context); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Setter.class); set.forEach(element -> { JCTree jcTree = trees.getTree(element); jcTree.accept(new TreeTranslator() { @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil(); for (JCTree tree : jcClassDecl.defs) { if (tree.getKind().equals(Tree.Kind.VARIABLE)) { JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree; jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl); } } jcVariableDeclList.forEach(jcVariableDecl -> { messager.printMessage(Diagnostic.Kind.NOTE, "set " + jcVariableDecl.getName() + " has been processed"); treeMaker.pos = jcVariableDecl.pos; jcClassDecl.defs = jcClassDecl.defs.prepend(makeSetterMethodDecl(jcVariableDecl)); }); super.visitClassDef(jcClassDecl); } }); }); return true; } private JCTree.JCMethodDecl makeSetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) { ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); statements.append( treeMaker.Exec( treeMaker.Assign( treeMaker.Select( treeMaker.Ident(names.fromString("this")), names.fromString(jcVariableDecl.name.toString()) ), treeMaker.Ident(names.fromString(jcVariableDecl.name.toString())) ) ) ); JCTree.JCBlock body = treeMaker.Block(0, statements.toList()); // 生成入参 JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), jcVariableDecl.getName(),jcVariableDecl.vartype, null); List<JCTree.JCVariableDecl> paramList = List.of(param); return treeMaker.MethodDef( treeMaker.Modifiers(Flags.PUBLIC), // 方法限定值 setNewMethodName(jcVariableDecl.getName()), // 方法名 treeMaker.Type(new Type.JCVoidType()), // 返回类型 List.nil(), paramList, // 入参 List.nil(), body, null ); } private Name setNewMethodName(Name name) { String s = name.toString(); return names.fromString("set" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length())); } }
如上我们就编写好了一个插入式处理器,此插入式处理器用于生成get/set方法
JCTree是语法树元素的基类,包含一个重要的字段pos,该字段用于指明当前语法树节点(JCTree)在语法树中的位置,因此我们不能直接用new关键字来创建语法树节点,及时创建了也没有意义,此外,结合访问者模式,将数据结构与数据的处理进行解耦,部分源码如下:
public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { public int pos = -1; ... public abstract void accept(JCTree.Visitor visitor); ... }
JCTree的子类:
1、JCStatement:声明语法树节点,常见的子类如下:
2、JCMethodDecl:方法定义语法树节点
3、JCModifiers:访问标志语法树节点
4、JCExpression:表达式语法树节点,常见子类如下:
TreeMaker用于创建一系列语法树节点,创建时会为创建出来的JCTree设置pos字段,所以必须用上下文相关的TreeMaker对象来创建语法树节点,而不能直接new语法树节点
源码可以参考:TreeMaker DOC
TreeMaker.Modifiers方法用于创建访问标志语法树节点(JCModifiers),源码如下:
public JCModifiers Modifiers(long flags) { return Modifiers(flags, List.< JCAnnotation >nil()); } public JCModifiers Modifiers(long flags, List<JCAnnotation> annotations) { JCModifiers tree = new JCModifiers(flags, annotations); boolean noFlags = (flags & (Flags.ModifierFlags | Flags.ANNOTATION)) == 0; tree.pos = (noFlags && annotations.isEmpty()) ? Position.NOPOS : pos; return tree; }
其中入参flags
可以用枚举类型com.sun.tools.javac.code.Flags
,且支持拼接(枚举值经过精心设计)
例子:treeMaker.Modifiers(Flags.PUBLIC + Flags.STATIC + Flags.FINAL);
TreeMaker.ClassDef用于创建类定义语法树节点(JCClassDecl),源码如下:
public JCClassDecl ClassDef(JCModifiers mods, Name name, List<JCTypeParameter> typarams, JCExpression extending, List<JCExpression> implementing, List<JCTree> defs) { JCClassDecl tree = new JCClassDecl(mods, name, typarams, extending, implementing, defs, null); tree.pos = pos; return tree; }
TreeMake.MethodDef用于创建语法定义语法树节点(JCMethodDecl),源码如下:
public JCMethodDecl MethodDef(JCModifiers mods, Name name, JCExpression restype, List<JCTypeParameter> typarams, List<JCVariableDecl> params, List<JCExpression> thrown, JCBlock body, JCExpression defaultValue) { JCMethodDecl tree = new JCMethodDecl(mods, name, restype, typarams, params, thrown, body, defaultValue, null); tree.pos = pos; return tree; } public JCMethodDecl MethodDef(MethodSymbol m, Type mtype, JCBlock body) { return (JCMethodDecl) new JCMethodDecl( Modifiers(m.flags(), Annotations(m.getAnnotationMirrors())), m.name, Type(mtype.getReturnType()), TypeParams(mtype.getTypeArguments()), Params(mtype.getParameterTypes(), m), Types(mtype.getThrownTypes()), body, null, m).setPos(pos).setType(mtype); }
其中,返回类型填null
或者treeMaker.TypeIdent(TypeTag.VOID)
都代表返回void类型
TreeMaker.VarDef用于创建字段/变量定义语法树节点(JCVariableDecl),源码如下:
public JCVariableDecl VarDef(JCModifiers mods, Name name, JCExpression vartype, JCExpression init) { JCVariableDecl tree = new JCVariableDecl(mods, name, vartype, init, null); tree.pos = pos; return tree; } public JCVariableDecl VarDef(VarSymbol v, JCExpression init) { return (JCVariableDecl) new JCVariableDecl( Modifiers(v.flags(), Annotations(v.getAnnotationMirrors())), v.name, Type(v.type), init, v).setPos(pos).setType(v.type); }
TreeMaker.Ident用于创建标识符语法树节点(JCIdent),源码如下:
public JCIdent Ident(Name name) { JCIdent tree = new JCIdent(name, null); tree.pos = pos; return tree; } public JCIdent Ident(Symbol sym) { return (JCIdent)new JCIdent((sym.name != names.empty) ? sym.name : sym.flatName(), sym) .setPos(pos) .setType(sym.type); } public JCExpression Ident(JCVariableDecl param) { return Ident(param.sym); }
TreeMaker.Return用于创建return语句语法树节点(JCReturn),源码如下:
public JCReturn Return(JCExpression expr) { JCReturn tree = new JCReturn(expr); tree.pos = pos; return tree; }
TreeMaker.Select用于创建域访问/方法访问(这里的方法访问只是取到名字,方法的调用需要调用TreeMaker.Apply)语法树节点(JCFieldAccess),源码如下:
public JCFieldAccess Select(JCExpression selected, Name selector) { JCFieldAccess tree = new JCFieldAccess(selected, selector, null); tree.pos = pos; return tree; } public JCExpression Select(JCExpression base, Symbol sym) { return new JCFieldAccess(base, sym.name, sym).setPos(pos).setType(sym.type); }
TreeMaker.NewClass用于创建new语句语法树节点(JCNewClass),源码如下:
public JCNewClass NewClass(JCExpression encl, List<JCExpression> typeargs, JCExpression clazz, List<JCExpression> args, JCClassDecl def) { JCNewClass tree = new JCNewClass(encl, typeargs, clazz, args, def); tree.pos = pos; return tree; }
TreeMaker.Apply用于创建方法调用语法树节点(JCMethodInvocation),源码如下:
public JCMethodInvocation Apply(List<JCExpression> typeargs, JCExpression fn, List<JCExpression> args) { JCMethodInvocation tree = new JCMethodInvocation(typeargs, fn, args); tree.pos = pos; return tree; }
TreeMaker.Assign用于创建赋值语句语法树节点(JCAssign),源码如下:
public JCAssign Assign(JCExpression lhs, JCExpression rhs) { JCAssign tree = new JCAssign(lhs, rhs); tree.pos = pos; return tree; }
TreeMaker.Exec用于创建可执行语句语法树节点(JCExpressionStatement),源码如下:
public JCExpressionStatement Exec(JCExpression expr) { JCExpressionStatement tree = new JCExpressionStatement(expr); tree.pos = pos; return tree; }
例如,TreeMaker.Apply以及TreeMaker.Assign就需要外面包一层TreeMaker.Exec来获得一个JCExpressionStatement
TreeMaker.Block用于创建组合语句语法树节点(JCBlock),源码如下:
public JCBlock Block(long flags, List<JCStatement> stats) { JCBlock tree = new JCBlock(flags, stats); tree.pos = pos; return tree; }
上述JSR-269API中会涉及到一个List,这个List不是java.util.List,它是com.sun.tools.javac.util.List,这个List的操作比较奇特,不支持链式操作。下面给出部分源码,List包含两个字段,head和tail,其中head只是一个节点,而tail是一个List
public class List<A> extends AbstractCollection<A> implements java.util.List<A> { public A head; public List<A> tail; private static final List<?> EMPTY_LIST = new List<Object>((Object)null, (List)null) { public List<Object> setTail(List<Object> var1) { throw new UnsupportedOperationException(); } public boolean isEmpty() { return true; } }; List(A head, List<A> tail) { this.tail = tail; this.head = head; } public static <A> List<A> nil() { return EMPTY_LIST; } public List<A> prepend(A var1) { return new List(var1, this); } public List<A> append(A var1) { return of(var1).prependList(this); } public static <A> List<A> of(A var0) { return new List(var0, nil()); } public static <A> List<A> of(A var0, A var1) { return new List(var0, of(var1)); } public static <A> List<A> of(A var0, A var1, A var2) { return new List(var0, of(var1, var2)); } public static <A> List<A> of(A var0, A var1, A var2, A... var3) { return new List(var0, new List(var1, new List(var2, from(var3)))); } ... }
由于com.sun.tools.javac.util.ListBuffer用起来不是很方便,而ListBuffer的行为与java.util.List的行为类似,并且提供了转换成com.sun.tools.javac.util.List的方法
ListBuffer<JCTree.JCStatement> jcStatements = new ListBuffer<>(); //添加语句 " this.xxx = xxx; " jcStatements.append(...); //添加Builder模式中的返回语句 " return this; " jcStatements.append(...); List<JCTree.JCStatement> lst = jcStatements.toList();
注意点:方法的名字就是
treeMaker.MethodDef( treeMaker.Modifiers(Flags.PUBLIC), //访问标志 names.fromString("
" ), //名字 treeMaker.TypeIdent(TypeTag.VOID), //返回类型 List.nil(), //泛型形参列表 List.nil(), //参数列表 List.nil(), //异常列表 jcBlock, //方法体 null //默认方法(可能是interface中的那个default) );
注意点:访问标志设置成:Flags.PARAMETER
treeMaker.VarDef( treeMaker.Modifiers(Flags.PARAMETER), //访问标志。极其坑爹!!! prototypeJCVariable.name, //名字 prototypeJCVariable.vartype, //类型 null //初始化语句 );
treeMaker.Exec( treeMaker.Assign( treeMaker.Select( treeMaker.Ident(names.fromString(THIS)), jcVariable.name ), treeMaker.Ident(jcVariable.name) ) )
treeMaker.NewClass( null, //尚不清楚含义 List.nil(), //泛型参数列表 treeMaker.Ident(builderClassName), //创建的类名 List.nil(), //参数列表 null //类定义,估计是用于创建匿名内部类 )
treeMaker.Exec( treeMaker.Apply( List.nil(), treeMaker.Select( treeMaker.Ident(getNameFromString(IDENTIFIER_DATA)), jcMethodDecl.getName() ), List.of(treeMaker.Ident(jcVariableDecl.getName())) //传入的参数集合 ) )
注意,直接拿vartype字段,而不是type字段或者**getType()**方法
【tip】:如果是maven项目需要在pom文件中配置如下内容:
目的:避免注解处理器编译出错问题
<build>
<resources>
<resource>
<directory>src/main/resourcesdirectory>
<excludes>
<exclude>META-INF/**/*exclude>
excludes>
resource>
resources>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.1version>
<configuration>
<source>${maven.compiler.target}source>
<target>${maven.compiler.target}target>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-resources-pluginartifactId>
<version>2.6version>
<executions>
<execution>
<id>process-METAid>
<phase>prepare-packagephase>
<goals>
<goal>copy-resourcesgoal>
goals>
<configuration>
<outputDirectory>target/classesoutputDirectory>
<resources>
<resource>
<directory>${basedir}/src/main/resources/directory>
<includes>
<include>**/*include>
includes>
resource>
resources>
configuration>
execution>
executions>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-source-pluginartifactId>
<version>3.0.1version>
<configuration>
<attach>trueattach>
configuration>
<executions>
<execution>
<phase>compilephase>
<goals>
<goal>jargoal>
goals>
execution>
executions>
plugin>
plugins>
build>
**注:**部分代码和灵感来源
Java-JSR-269-插入式注解处理器