AbstractProcessor相关的API记录
java文件操作相关的两个类: JCTree 树节点、TreeMaker 树节点构建器。
JCTree
JCTree的一个子类就是java语法中的一个节点,类、方法、字段等这些都被封装成了一个JCTree子类。
JCTree详细的介绍:抽象语法树AST的全面解析(二) - 简书
TreeMaker
TreeMaker 里的方法用于构建JCTree某个子类,例:treeMaker.MethodDef(…)返回值是JCTree的子类JCMethodDecl(方法节点)
TreeMaker创建各种节点的方法如下:
// 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));
如以下代码是给一个方法新增一句话System.out.println("hello world")
package jst;
import com.google.auto.service.AutoService;
import com.sun.tools.javac.model.JavacElements;
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 javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.util.Set;
/**
* @author wuweifeng wrote on 2022/12/21
* @version 1.0
*/
@SupportedAnnotationTypes("jst.HelloWorld")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elements = roundEnv.getRootElements();
final Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
final JavacElements elementUtils = (JavacElements) processingEnv.getElementUtils();
final TreeMaker treeMaker = TreeMaker.instance(context);
for (Element element : roundEnv.getElementsAnnotatedWith(HelloWorld.class)) {
JCTree.JCMethodDecl jcMethodDecl = (JCTree.JCMethodDecl) elementUtils.getTree(element);
treeMaker.pos = jcMethodDecl.pos;
jcMethodDecl.body = treeMaker.Block(0, List.of(
treeMaker.Exec(
treeMaker.Apply(
List.nil(),
treeMaker.Select(
treeMaker.Select(
treeMaker.Ident(
elementUtils.getName("System")
),
elementUtils.getName("out")
),
elementUtils.getName("println")
),
List.of(
treeMaker.Literal("Hello, world!!!")
)
)
),
jcMethodDecl.body
));
}
return false;
}
}
使用时
@HelloWorld
public static void main(String[] args) {
System.out.println("demo打印");
}
加上了@HelloWorld注解后,运行就会打印
现在针对MyProcessor类里的内容做个解释。
jcMethodDecl.body即为方法体,利用treemaker的Block方法获取到一个新方法体,将原来的替换掉。就达到了修改方法体的目的了。这里的Block方法有两个参数,重点要关注的是第二个参数,也就是具体的方法体内容。它是一个List类型的参数,List里面每一个元素就代表一个语句块,比如,例子中有两块语句,第一块是我们织入的代码块:System.out.println("Hello, world!!!");用treeMaker.Exec()来实现,第二块是原来的代码块:jcMethodDecl.body。这块的代码是用户原本的代码,我们直接放进来就行。这个List是有顺序的,谁的顺序在前,谁最终生成的代码块就在前,比如这里我们织入的代码在原来的代码块之前,所以最终生成System.out.println("Hello, world!!!");语句就在该方法的第一行位置。
重点关注的应该是treeMaker.Exec()这个方法,这个方法帮助我们最终生成了System.out.println("Hello, world!!!");这条语句,它的参数是treeMaker.Apply这个方法的返回结果,这个方法的第二个参数,也就是最终实现了输出System.out.println("Hello, world!!!")的东西。
这里面用到了两个方法,一个是treeMaker.Select(生成具体的方法),一个是treeMaker.Literal(方法的参数)。treeMaker.Select里面套了很多层,对比两种写法的区别,你也能明白,这是为了写出多级方法的做法,多级方法的第一级以treeMaker.Ident开始,然后一层套一层,直到整个方法结束。
如何debug processor代码
a)idea右上角,Edit Configurations
b)点击左上角的+号,选择Remote,或者如图的Remote JVM debug
c)随便起一个名字,比如ProcessorDebug,如图,点击确定。
d)进入Terminal界面,输入mvnDebug clean install,回车,不出意外的话,会出现如图的提示
f)process里面打上断点,然后点击debug按钮,便命中了断点
异常情况
如果在rebuild project时出现:java: java.lang.ClassCastException: com.sun.proxy.$Proxy26 cannot be cast to com.sun.tools.javac.processing.JavacProcessingEnvironment
则在设置里添加如下内容:
-Djps.track.ap.dependencies=false