java AbstractProcessor 编译时注解(API)

写在前面

接上一篇文章:java AbstractProcessor 编译时注解(入手体验)

1、编译时注解原理

在java文件编译成class文件之前,对java文件进行操作,生成代码。
AbstractProcessor 完成的就是对java文件进行操作的相关功能。

相关知识点:

1、java文件操作相关的两个类: JCTree 树节点、TreeMaker 树节点构建器
2、JCTree 的一个子类就是java语法中的一个节点,类、方法、字段等这些都被封装成了一个JCTree子类。
3、TreeMaker 里的一个方法用于构建JCTree某个子类,例:treeMaker.MethodDef(…)返回值是JCTree的子类JCMethodDecl(方法节点)

2、AbstractProcessor 解析

一个编译时注解,最后对java文件进行的操作,都在 AbstractProcessor 子类中编写。
在这里插入图片描述

  1. @SupportedAnnotationTypes(“cc.HelloWorld”) ,“cc.HelloWorld” 是HelloWorld注解的类全名
  2. @SupportedSourceVersion(SourceVersion.RELEASE_7) ,annotation支持版本,向前兼容到JDK7
  3. @AutoService(Processor.class),让AbstractProcessor子类被编译器发现
  4. annotations 传进来的注解列表
  5. roundEnv 环境,可以从它获取被注解标记的元素,然后获取元素的JCTree对象。
  6. process 方法返回值,涉及到编译器的处理逻辑,返回true表示已经处理,返回false表示未被处理

注意AbstractProcessor每一次有修改,就要重新编译该类。或者有修改就删除target,让编译器重新生成class文件

2.1、RoundEnvironment

process 方法的第二个参数RoundEnvironment roundEnv,可以从里面获取java文件的树结构,并往里面加入代码。
RoundEnvironment 的方法:

方法名说明
errorRaised()如果在前一轮处理中引发错误,则返回true ; 否则返回false 。
processingOver()如果此轮生成的类型不受后续轮注释处理的影响,则返回true ; 否则返回false 。
getRootElements()返回前一轮生成的注释处理的root elements 。
getElementsAnnotatedWith()返回被某个注解标记的元素。
getElementsAnnotatedWithAny()返回多个注解标记的元素。

2.2、获取Messager、JavacTrees、TreeMaker、Names、elementUtils

这些对象中,JavacTrees和TreeMaker是一定会用到的

@SupportedAnnotationTypes("这里填注解类的全名")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
    Messager messager;                 // 用于在编译器打印消息的组件
    Names names;                       // 用于创建标识符的对象
    JavacTrees trees;                  // 语法树
    JavacElements elementUtils;        // trees 和 elementUtils都可以获取 元素的JCTree对象
    TreeMaker treeMaker;               // 用来构造语法树节点

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        Context context = ((JavacProcessingEnvironment) this.processingEnv).getContext();
        this.elementUtils = (JavacElements) this.processingEnv.getElementUtils();
        this.messager = this.processingEnv.getMessager();
        this.names = Names.instance(this.context);
        this.trees = JavacTrees.instance(this.processingEnv);
        this.treeMaker = TreeMaker.instance(this.context);
    }
    
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
		...
        return true;
    }

这里有个坑,获取Messager、JavacTrees、TreeMaker这些对象只能在init()方法里面或者init()方法执行之后,否则获取不到这些对象,如下错误示例。
在这里插入图片描述

2.3、JCTree

JCTree 的一个子类就是java语法中的一个节点,类、方法、字段等这些都被封装成了一个JCTree子类。
JCTree 从JavacTrees中获取,如下例:

    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> elementSet = roundEnv.getElementsAnnotatedWith(HelloWorld.class);

		elementSet.forEach(element -> {
            //获取元素的JCTree对象
            JCTree jcTree = trees.getTree(element);               // JCTree也可以使用elementUtils获取: JCTree jcTree = elementUtils.getTree(element);
            ...
            });
        return true;
    }

JCTree 是语法树的基类,包含子类:

父类子类
JCTree声明节点 JCStatement、方法定义节点 JCMethodDecl、访问标志节点 JCModifiers、表达式节点 JCExpression
JCStatement 声明节点语块JCBlock、返回语句JCReturn、类定义JCClassDecl、 字段定义JCVariableDecl
JCExpression 表达式节点赋值语句语JCAssign、变量,类型,关键字JCIdent
JCMethodDecl 方法节点
JCModifiers 访问标识节点

在这里插入图片描述

2.3.1、pos和defs

jcTree.pos 用于指明当前节点在语法树中的位置,treeMaker也有这个字段。
jcTree.defs 类定义的详细语句,包括字段,方法定义等,treeMaker也有这个字段。

2.3.2、JCTree访问者模式

如果你对访问者模式没有了解,建议先阅读这篇文章:访问者模式一篇就够了

JCTree jcTree = trees.getTree(element); 
// JCTree利用的是访问者模式,将数据与数据的处理进行解耦,TreeTranslator是访问者,这里可以重写访问类时的逻辑
jcTree.accept(new TreeTranslator(){...});

2.4、TreeMaker

TreeMaker 里的一个方法用于构建JCTree某个子类,例:treeMaker.MethodDef(…)返回值是JCTree的子类JCMethodDecl(方法节点)。

参考官方文档:TreeMaker文档

TreeMaker创建各种节点的方法如下:

方法名说明
Modifiers()创建访问标志
ClassDef()类定义
MethodDef()方法定义
VarDef()创建字段/变量
Ident()创建标识符
Return()返回语句
Select()创建域访问/方法访问
NewClass()创建new语句
Apply()创建方法调用
Assign()创建赋值语句
Exec()创建可执行语句
Block()创建组合语句

示例:

// 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)); 

3、实例一:@NoArgsConstructor 添加无参构造函数

本实例未使用JCTree访问者模式,直接添加一个无参构造函数到jcTree.defs中

新建项目a(使用注解)、b(写编译时注解功能)

3.1、pom

项目b的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
         
    <artifactId>b</artifactId>
    <groupId>org.example</groupId>
    <version>1.0</version>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>

        <!-- 提供@AutoService注解,让AbstractProcessor子类被编译器发现 -->
        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.0-rc5</version>
        </dependency>

        <!-- 提供JCTree等一些功能api -->
        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.8</version>
            <scope>system</scope>
            <systemPath>${java.home}/../lib/tools.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>commons-validator</groupId>
            <artifactId>commons-validator</artifactId>
            <version>1.3.1</version>
        </dependency>

    </dependencies>

</project>

项目a的poml.xml(引入项目b)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <artifactId>a</artifactId>
    <groupId>org.example</groupId>
    <version>1.0</version>
    <modelVersion>4.0.0</modelVersion>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>

        <dependency>
            <groupId>org.example</groupId>
            <artifactId>b</artifactId>
            <version>1.0</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>

3.2、代码

@NoArgsConstructor

package cc;

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 NoArgsConstructor {
}

NoArgsConstructorProcessor.java

package cc;

import com.google.auto.service.AutoService;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.TypeTag;
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 com.sun.tools.javac.util.Names;
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("cc.NoArgsConstructor")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class NoArgsConstructorProcessor extends AbstractProcessor {
    JavacTrees trees;
    TreeMaker treeMaker;
    Names names;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        Context context = ((JavacProcessingEnvironment) this.processingEnv).getContext();
        this.trees = JavacTrees.instance(this.processingEnv);
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> elementsSet = roundEnv.getElementsAnnotatedWith(NoArgsConstructor.class);
        elementsSet.forEach(element -> {
            JCTree.JCClassDecl jcTree = (JCTree.JCClassDecl) trees.getTree(element);
            jcTree.defs = jcTree.defs.append(
                    treeMaker.MethodDef(
                        treeMaker.Modifiers(Flags.PUBLIC),
                        names.fromString("<init>"),
                        treeMaker.TypeIdent(TypeTag.VOID),
                        List.nil(),
                        List.nil(),
                        List.nil(),
                        treeMaker.Block(0, List.nil()),
                        null)
            );
        });
        return true;
    }
}

项目a中使用@NoArgsConstructor

import cc.NoArgsConstructor;

@NoArgsConstructor
public class Test {

    public Test(String s){}
    
    public static void main(String[] args) {}
}

Test编译后的class
在这里插入图片描述

4、实例二:@AllArgsConstructor 添加全参构造函数

本实例使用JCTree访问者模式。

新建项目a(使用注解)、b(写编译时注解功能)

4.1、pom

同 3.1、pom

4.2、代码

@AllArgsConstructor

package cc;

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 AllArgsConstructor {
}

AllArgsConstructorProcessor.java

package cc;

import com.google.auto.service.AutoService;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.TypeTag;
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.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Names;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import java.util.Set;

@SupportedAnnotationTypes("cc.AllArgsConstructor")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class AllArgsConstructorProcessor extends AbstractProcessor {
    JavacTrees trees;
    TreeMaker treeMaker;
    Names names;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        Context context = ((JavacProcessingEnvironment) this.processingEnv).getContext();
        this.trees = JavacTrees.instance(this.processingEnv);
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
    }

    /**
     * 字段的语法树节点的集合
     */
    private List<JCTree.JCVariableDecl> fieldJcVariables;


    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(AllArgsConstructor.class);
        set.forEach(element -> {
            JCTree jcTree = trees.getTree(element);
            jcTree.accept(new TreeTranslator() {

                @Override
                public void visitClassDef(JCTree.JCClassDecl jcClass) {
                    before(jcClass);

                    //添加全参构造方法
                    jcClass.defs = jcClass.defs.append(
                                createAllArgsConstructor()
                    );
                    after();
                }
            });
        });

        return true;
    }

    /**
     * 判断是否是合法的字段
     *
     * @param jcTree 语法树节点
     * @return 是否是合法字段
     */
    private static boolean isValidField(JCTree jcTree) {
        if (jcTree.getKind().equals(JCTree.Kind.VARIABLE)) {
            JCTree.JCVariableDecl jcVariable = (JCTree.JCVariableDecl) jcTree;

            Set<Modifier> flagSets = jcVariable.mods.getFlags();
            return (!flagSets.contains(Modifier.STATIC)
                    && !flagSets.contains(Modifier.FINAL));
        }

        return false;
    }

    /**
     * 进行一些初始化工作
     *
     * @param jcClass 类的语法树节点
     */
    private void before(JCTree.JCClassDecl jcClass) {
        ListBuffer<JCTree.JCVariableDecl> jcVariables = new ListBuffer<>();

        //遍历jcClass的所有内部节点,可能是字段,方法等等
        for (JCTree jcTree : jcClass.defs) {
            //找出所有set方法节点,并添加
            if (isValidField(jcTree)) {
                //注意这个com.sun.tools.javac.util.List的用法,不支持链式操作,更改后必须赋值
                jcVariables.append((JCTree.JCVariableDecl) jcTree);
            }
        }
        this.fieldJcVariables = jcVariables.toList();
    }

    /**
     * 清理
     */
    private void after() {
        this.fieldJcVariables = null;
    }

    /**
     * 创建全参数构造方法
     *
     * @return 全参构造方法语法树节点
     */
    private JCTree.JCMethodDecl createAllArgsConstructor() {
        ListBuffer<JCTree.JCStatement> jcStatements = new ListBuffer<>();
        for (JCTree.JCVariableDecl jcVariable : fieldJcVariables) {
            Name name = names.fromString(jcVariable.name.toString());
            JCTree.JCIdent thisIdent = treeMaker.Ident(names.fromString("this"));

            JCTree.JCFieldAccess select = treeMaker.Select(thisIdent, name);
            JCTree.JCIdent ident = treeMaker.Ident(names.fromString(jcVariable.name.toString()));

            //添加构造方法的赋值语句 " this.xxx = xxx; "
            jcStatements.append(
                    treeMaker.Exec(treeMaker.Assign(select, ident))
            );
        }

        return treeMaker.MethodDef(
                treeMaker.Modifiers(Flags.PUBLIC),
                names.fromString("<init>"),
                treeMaker.TypeIdent(TypeTag.VOID),
                List.nil(),
                cloneJCVariablesAsParams(treeMaker, fieldJcVariables),
                List.nil(),
                treeMaker.Block(0 , jcStatements.toList()),
                null
        );
    }

    /**
     * 克隆一个字段的语法树节点集合,作为方法的参数列表
     *
     * @param treeMaker            语法树节点构造器
     * @param prototypeJCVariables 字段的语法树节点集合
     * @return 方法参数的语法树节点集合
     */
    static List<JCTree.JCVariableDecl> cloneJCVariablesAsParams(TreeMaker treeMaker, List<JCTree.JCVariableDecl> prototypeJCVariables) {
        ListBuffer<JCTree.JCVariableDecl> jcVariables = new ListBuffer<>();
        for (JCTree.JCVariableDecl jcVariable : prototypeJCVariables) {
            jcVariables.append(cloneJCVariableAsParam(treeMaker, jcVariable));
        }
        return jcVariables.toList();
    }

    /**
     * 克隆一个字段的语法树节点,该节点作为方法的参数
     * 具有位置信息的语法树节点是不能复用的!
     *
     * @param treeMaker           语法树节点构造器
     * @param prototypeJCVariable 字段的语法树节点
     * @return 方法参数的语法树节点
     */
    static JCTree.JCVariableDecl cloneJCVariableAsParam(TreeMaker treeMaker, JCTree.JCVariableDecl prototypeJCVariable) {
        return treeMaker.VarDef(
                treeMaker.Modifiers(Flags.PARAMETER), //访问标志。极其坑爹!!!
                prototypeJCVariable.name, //名字
                prototypeJCVariable.vartype, //类型
                null //初始化语句
        );
    }
}

项目a中使用@AllArgsConstructor

import cc.AllArgsConstructor;

@AllArgsConstructor
public class Test {
    private String a;
    private String b;

    public static void main(String[] args) {}
}

编译后的class文件
在这里插入图片描述

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AbstractProcessor Demo 是一个示例程序,用于展示 Java 编译器的 Annotation Processing 工具的使用。Annotation Processing 工具是 Java 编译器的一部分,它可以在编译处理源代码中的注解。 在 AbstractProcessor Demo 中,我们创建了一个自定义注解 @MyAnnotation,并使用注解处理器来处理该注解注解处理器继承自 javax.annotation.processing.AbstractProcessor 类,并重写了其中的一些方法。 在处理器中,我们将注解处理器的处理目标设置为 ELEMENT_TYPE_ANNOTATION_TYPE,表示该注解处理器仅处理注解类型的元素。通过重写 process() 方法,我们可以获取到被 @MyAnnotation 注解修饰的元素,并对其进行相应的处理。 在 Demo 中,我们将被 @MyAnnotation 注解修饰的类的完整类名打印出来,以展示注解处理器的基本功能。这个打印操作是在 process() 方法中完成的。 使用 AbstractProcessor Demo,我们可以学习如何创建和应用注解,了解注解处理器的基本用法,并且可以在此基础上进行更深入的学习和实践。注解处理器可以用于自动生成代码、进行静态分析和验证等一系列任务,对于提升开发效率和代码质量有很大的帮助。 总之,AbstractProcessor Demo 是一个用于演示 Annotation Processing 工具的示例程序,它展示了如何创建一个自定义的注解处理器,并使用该处理器来处理指定注解。通过分析和理解 Demo 可以帮助我们更好地使用和理解注解处理器的功能和特性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值