AbstractProcessor相关的API记录

Posted 天涯泪小武

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

 

以上是关于AbstractProcessor相关的API记录的主要内容,如果未能解决你的问题,请参考以下文章

AbstractProcessor相关的API记录

javajavac 相关API AbstractProcessor

javajavac 相关API JavaCompiler StandardJavaFileManager AbstractProcessor

AbstractProcessor: 利用注解动态生成代码

基于AbstractProcessor扩展MapStruct自动生成实体映射工具类

java 注解处理器(AbstractProcessor) 获取到 指定注解的属性值 javapoet 如何使用这个值生成类?