美文网首页
jsr269抽象语法树操作API编译期注解处理-简单demo

jsr269抽象语法树操作API编译期注解处理-简单demo

作者: 东南枝下 | 来源:发表于2021-08-01 01:04 被阅读0次

来自- 《深入理解JVM字节码》

JDK1.6引入了JSR269规范,允许在编译期处理注解,读取、修改、添加抽象语法树中的内容。
lombok插件就是应用了这个

1、自定义注解

package com.jenson.annotation;

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

2、处理类,继承AbstractProcessor

package com.jenson.annotation;

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 java.util.Set;

@SupportedAnnotationTypes("com.jenson.annotation.MyData")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyDataAnnotationProcessor extends AbstractProcessor {

    private JavacTrees javacTrees;
    private TreeMaker treeMaker;
    private Names names;

    /**
     * 从Context中初始化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);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 获取注解类的集合,之后依次去处理
        Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(MyData.class);
        for (Element element : set) {
            // 获取当前类的抽象语法树
            JCTree tree = javacTrees.getTree(element);
            // 获取抽象语法树的所有节点
            // Visitor 抽象内部类,内部定义了访问各种语法节点的方法
            tree.accept(new TreeTranslator() {
                @Override
                public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                    jcClassDecl.defs.stream()
                            // 过滤,只处理变量类型
                            .filter(it -> it.getKind().equals(Tree.Kind.VARIABLE))
                            // 类型强转
                            .map(it -> (JCTree.JCVariableDecl) it)
                            .forEach(it -> {
                                // 添加get方法
                                jcClassDecl.defs = jcClassDecl.defs.prepend(genGetterMethod(it));
                                // 添加set方法
                                jcClassDecl.defs = jcClassDecl.defs.prepend(genSetterMethod(it));
                            });

                    super.visitClassDef(jcClassDecl);
                }
            });

        }


        return true;
    }

    private JCTree.JCMethodDecl genGetterMethod(JCTree.JCVariableDecl jcVariableDecl) {
        // 生成return语句,return this.xxx
        JCTree.JCReturn returnStatement = treeMaker.Return(
                treeMaker.Select(
                        treeMaker.Ident(names.fromString("this")),
                        jcVariableDecl.getName()
                )
        );

        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<JCTree.JCStatement>().append(returnStatement);

        // public 方法访问级别修饰
        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
        // 方法名 getXXX ,根据字段名生成首字母大写的get方法
        Name getMethodName = createGetMethodName(jcVariableDecl.getName());
        // 返回值类型,get类型的返回值类型与字段类型一致
        JCTree.JCExpression returnMethodType = jcVariableDecl.vartype;
        // 生成方法体
        JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
        // 泛型参数列表
        List<JCTree.JCTypeParameter> methodGenericParamList = List.nil();
        // 参数值列表
        List<JCTree.JCVariableDecl> parameterList = List.nil();
        // 异常抛出列表
        List<JCTree.JCExpression> throwCauseList = List.nil();

        // 生成方法定义树节点
        return treeMaker.MethodDef(
                // 方法访问级别修饰符
                modifiers,
                // get 方法名
                getMethodName,
                // 返回值类型
                returnMethodType,
                // 泛型参数列表
                methodGenericParamList,
                //参数值列表
                parameterList,
                // 异常抛出列表
                throwCauseList,
                // 方法默认体
                body,
                // 默认值
                null
        );

    }

    private JCTree.JCMethodDecl genSetterMethod(JCTree.JCVariableDecl jcVariableDecl) {
        // this.xxx=xxx
        JCTree.JCExpressionStatement statement = treeMaker.Exec(
                treeMaker.Assign(
                        treeMaker.Select(
                                treeMaker.Ident(names.fromString("this")),
                                jcVariableDecl.getName()
                        ),
                        treeMaker.Ident(jcVariableDecl.getName())
                )
        );

        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<JCTree.JCStatement>().append(statement);

        // set方法参数
        JCTree.JCVariableDecl param = treeMaker.VarDef(
                // 访问修饰符
                treeMaker.Modifiers(Flags.PARAMETER, List.nil()),
                // 变量名
                jcVariableDecl.name,
                //变量类型
                jcVariableDecl.vartype,
                // 变量初始值
                null
        );

        // 方法访问修饰符 public
        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
        // 方法名(setXxx),根据字段名生成首选字母大写的set方法
        Name setMethodName = createSetMethodName(jcVariableDecl.getName());
        // 返回值类型void
        JCTree.JCExpression returnMethodType = treeMaker.Type(new Type.JCVoidType());
        // 生成方法体
        JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
        // 泛型参数列表
        List<JCTree.JCTypeParameter> methodGenericParamList = List.nil();
        // 参数值列表
        List<JCTree.JCVariableDecl> parameterList = List.of(param);
        // 异常抛出列表
        List<JCTree.JCExpression> throwCauseList = List.nil();
        // 生成方法定义语法树节点
        return treeMaker.MethodDef(
                // 方法级别访问修饰符
                modifiers,
                // set 方法名
                setMethodName,
                // 返回值类型
                returnMethodType,
                // 泛型参数列表
                methodGenericParamList,
                // 参数值列表
                parameterList,
                // 异常抛出列表
                throwCauseList,
                // 方法体
                body,
                // 默认值
                null
        );

    }

    private Name createGetMethodName(Name variableName) {
        String fieldName = variableName.toString();
        return names.fromString("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1));

    }

    private Name createSetMethodName(Name variableName) {
        String fieldName = variableName.toString();
        return names.fromString("set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1));

    }
}

3、定义使用该注解的类

package com.jenson.user;

import com.jenson.annotation.MyData;

@MyData
public class User {

    private Long id;
    private String name;

}

4、先去掉@MyData注解,编译,查看字节码如下,并没有getter、setter方法

localhost:user Jenson$ javap -c User.class 
Compiled from "User.java"
public class com.jenson.user.User {
  public com.jenson.user.User();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
}

5、先编译注解类,再编译User.java

# 编译注解包,我这一步报错了,找不到包,估计是ClassPath有问题,用IntellijIDEA编译通过的
javac -cp ./jsr269api/src/main/java/com/jenson/annotation/* -d ./hotchpotch/jsr269api/target/classes/
# 编译User类,这一步没问题
javac -cp ./jsr269api/target/classes -d ./jsr269api/target/classes -processor com.jenson.annotation.MyDataAnnotationProcessor ./jsr269api/src/main/java/com/jenson/user

6、查看字节文件,有了getter、setter方法

localhost:user Jenson$ javap -c User.class 
Compiled from "User.java"
public class com.jenson.user.User {
  public void setName();
    Code:
       0: aload_0
       1: aload_0
       2: getfield      #1                  // Field name:Ljava/lang/String;
       5: putfield      #1                  // Field name:Ljava/lang/String;
       8: return

  public java.lang.String getName();
    Code:
       0: aload_0
       1: getfield      #1                  // Field name:Ljava/lang/String;
       4: areturn

  public void setId();
    Code:
       0: aload_0
       1: aload_0
       2: getfield      #2                  // Field id:Ljava/lang/Long;
       5: putfield      #2                  // Field id:Ljava/lang/Long;
       8: return

  public java.lang.Long getId();
    Code:
       0: aload_0
       1: getfield      #2                  // Field id:Ljava/lang/Long;
       4: areturn

  public com.jenson.user.User();
    Code:
       0: aload_0
       1: invokespecial #3                  // Method java/lang/Object."<init>":()V
       4: return
}

7、打成jar包

MyDataAnnotationProcessor上加上注解@AutoService(Processor.class),这个注解来自谷歌的一个依赖,可以自动添加META-INF/services/javax.annotation.processing.Processor文件,就不用手动写了。
pom.xml 如下

...
<dependencies>
        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.0-rc5</version>
        </dependency>
        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.7</version>
            <scope>system</scope>
            <systemPath>/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/tools.jar</systemPath>
        </dependency>
    </dependencies>

    <build>
        <finalName>my-data</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
...

最后打包 mvn package

相关文章

  • jsr269抽象语法树操作API编译期注解处理-简单demo

    来自- 《深入理解JVM字节码》 JDK1.6引入了JSR269规范,允许在编译期处理注解,读取、修改、添加抽象语...

  • 人生苦短,我用Lombok

    编者按 得益于JSR269 api标准,我们可以愉快地在编译期处理注解。 目录 一、 常用注解 二、 使用方法 ...

  • 编译时注解学习二之 注解处理器初探AbstractProcess

    1注解处理器介绍 使用注解可以方便开发,因为编译期注解处理器会根据注解自动帮使用者完成很多重复性操作。什么叫编译期...

  • 类的加载

    可执行程序生成过程 预编译:展开宏,头文件,生成.i文件 编译:生成抽象语法树AST,AST 是抽象语法树,结构上...

  • java文件编译为class文件步骤

    1:词法分析+语法分析(代码字符串变为token序列,token生成抽象语法树) 2:注解处理(用户自定义的ann...

  • JS编译——AST

    JS编译——AST AST 抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Synt...

  • lombok原理

    为什么lombok能实现如此便捷的功能呢?这得益于JSR269规范,这个规范中约定了可以在编译时期支持注解处理功能...

  • 抽象语法树AST的全面分析(三)

    AST操作 抽象语法树AST的全面分析(一)抽象语法树AST的全面分析(二)前面两篇文章写到了抽象语法树的生成过程...

  • Android APT 实践

    APT->Annotation Processing Tool即注解处理器,编译期处理注解进而生成代码的工具。 常...

  • java 注解

    运行时的注解如何被处理的 [举例] java虚拟机运行api反射机制运行注解 注解语法 注解元素类型. 基本类型....

网友评论

      本文标题:jsr269抽象语法树操作API编译期注解处理-简单demo

      本文链接:https://www.haomeiwen.com/subject/phllvltx.html