JSR 269插件化注解处理实践(以模拟Lombok自动生成getter/setter为例)

简介: JSR 269是jdk1.6中引入的,在此之前,开发者只能在运行期对注解进行处理,而JSR 269允许开发这在编译期对注解进行处理,以此可以完成很多java不支持的特性,甚至创造新的语法糖。大名鼎鼎的lombok就是基于JSR 269来实现的。1. 原理介绍javac的编译过程如下图所示:可以看到JSR 269,也就是注解处理在编译产生抽象语法树AST之后,在此处插入一些逻辑,并且可以对AST进行

JSR 269是jdk1.6中引入的,在此之前,开发者只能在运行期对注解进行处理,而JSR 269允许开发这在编译期对注解进行处理,以此可以完成很多java不支持的特性,甚至创造新的语法糖。大名鼎鼎的lombok就是基于JSR 269来实现的。

1. 原理介绍

javac的编译过程如下图所示:

可以看到JSR 269,也就是注解处理在编译产生抽象语法树AST之后,在此处插入一些逻辑,并且可以对AST进行修改。因为JSR 269注解处理是在attr(语义分析,各种静态检查都是在这里做的)之前的,所以我们甚至可以将一颗不符合java语义的AST处理成符合意义的AST,从而完成一些cool operation。

jdk提供了一套操作抽象语法树的API,有三个核心类

  • Names类提供访问AST中标识符的方法。
  • 例如访问this标识符,name.fromString("this")
  • JCTree是代表各种AST节点的基类,其有很多子类,如JCStatement,JCExpression,JCMethodDecl,JCModifier等。
  • JCStatement是用来代表java声明语句的,它也有很多子类,如JCReturn,JCBlock(AST中的代码块节点,对应到java中就是中括号包含的一段代码),JCClassDecl(AST中的类声明节点),JCVariableDecl(AST中的变量声明节点),JCTry,JCIf,JCFroLoop(AST中的循环语句节点)等。
    • JCExpression用来代表表达式语句节点,常用的子类有JCAssign(赋值语句,如x=1),JCIdent(标识符语句),JCBinary(二元运算语句,如1+2),JCLiteral(常量语句)。
    • JCMethodDecl用于代表方法定义

  • JCModifiers代表AST访问标记节点,其有一个flag属性,取值如下:

  • TreeMaker封装了操作AST节点的方法。其拥有丰富的创建各种AST的方法,常用的有
  • Binary,生成二元表达式节点,如1+2:

treeMaker.Binary(JCTree.Tag.PLUS,  treeMaker.Literal(1),  treeMaker.Literal(2));

  • Ident,用于创建标识符节点,如获取标识符x:

treeMaker.Ident(names.fromString("x"));

  • Select,创建一个字段或方法,或访问某个Field,如this.id对应的Select代码为

treeMaker.Select(treeMaker.Ident(names.fromString("this"))names.fromString("id"));

  • Assign,生成一个赋值语句节点,如x=0:

treeMaker.Assign(     treeMaker.Ident(names.fromString("x"))//左表达式     treeMaker.Literal(0//右表达式 );

  • Exec,用于包装JCAssign对象,使其返回一个JCExpressionStatement,JCExpressionStatement就可以直接用来生成方法AST节点了,如下

treeMaker.Exec(     treeMaker.Assign(         treeMaker.Ident(names.fromString("x"))//左表达式         treeMaker.Literal(0//右表达式     ) );

2. 自定义MyData注解实现getter/setter自动生成

下面来模拟lombok的Data注解的实现,来看一下JSR 269应该如何进行实践。

先声明MyData注解。

package com.xycode.techlecture.jsr269;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author: xycode
 * @email: lianguang.xy@alibaba-inc.com
 * @date: 2022/7/12
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface MyData {
}

接下来创建注解处理器类,jdk中有一个AbstractProcessor,我们只需继承它,然后编写字节的注解处理逻辑,目前是使得标注了MyData注解的类能够自动支持getter、setter方法。如下:

package com.xycode.techlecture.jsr269;

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

import com.sun.source.tree.Tree.Kind;
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.JCTree.JCClassDecl;
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.ListBuffer;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;

/**
 * 注解处理器实现(编译期执行), 实现类型lombok的@Data注解功能
 *
 * @author: xycode
 * @email: lianguang.xy@alibaba-inc.com
 * @date: 2022/7/12
 */
//SupportedAnnotationTypes用于指定改processor支持的注解
@SupportedAnnotationTypes("com.xycode.techlecture.jsr269.MyData")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyDataAnnotationProcessor extends AbstractProcessor {

    /**
     * 描述语法树的实例类
     */
    private JavacTrees javacTrees;

    /**
     * 创建语法树节点的工具类
     */
    private TreeMaker treeMaker;

    /**
     * 访问语法树中的标识符
     * eg: names.fromString("str")
     */
    private Names names;

    /**
     * 从AST上下文中初始化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);

    }

    /**
     * 生成getter方法节点
     */
    private JCTree.JCMethodDecl genGetterMethod(JCTree.JCVariableDecl jcVariableDecl) {
        Name variableDeclName = jcVariableDecl.getName();
        //生成语句: return this.xxx
        JCTree.JCReturn returnStatement = treeMaker.Return(
            treeMaker.Select(
                treeMaker.Ident(names.fromString("this")), variableDeclName
            )
        );
        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<JCTree.JCStatement>().append(returnStatement);

        //生成public修饰符
        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);

        //拼接方法名 getXxx
        String getMethodNameStr = "get" + variableDeclName.toString().substring(0, 1).toUpperCase()
            + variableDeclName.toString().substring(1);
        Name getMethodName = names.fromString(getMethodNameStr);

        //生成返回类型标识
        JCTree.JCExpression returnMethodType = jcVariableDecl.vartype;

        //生成方法体
        JCTree.JCBlock body = treeMaker.Block(0, statements.toList());

        //生成泛型参数列表
        com.sun.tools.javac.util.List<JCTree.JCTypeParameter> methodGenericParameterList
            = com.sun.tools.javac.util.List.nil();

        //生成参数值列表
        com.sun.tools.javac.util.List<JCTree.JCVariableDecl> methodParameterValList
            = com.sun.tools.javac.util.List.nil();

        //生成抛出的异常列表
        com.sun.tools.javac.util.List<JCTree.JCExpression> throwExceptionList = com.sun.tools.javac.util.List.nil();

        //生成方法定义AST节点
        return treeMaker.MethodDef(modifiers,  //public
            getMethodName, //方法名: getXxx
            returnMethodType, //返回类型
            methodGenericParameterList, //泛型参数列表
            methodParameterValList, //参数值列表
            throwExceptionList, //抛出异常列表
            body, //方法体
            null);
    }

    /**
     * 生成setter方法节点
     */
    private JCTree.JCMethodDecl genSetterMethod(JCTree.JCVariableDecl jcVariableDecl) {
        Name variableDeclName = jcVariableDecl.getName();
        //生成语句: this.xxx = xxx
        JCTree.JCExpressionStatement statement = treeMaker.Exec(
            treeMaker.Assign(
                treeMaker.Select(
                    treeMaker.Ident(names.fromString("this")), variableDeclName
                ),//左表达式部分: this.xxx
                treeMaker.Ident(variableDeclName) //右表达式部分: xxx
            )
        );
        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<JCTree.JCStatement>().append(statement);

        //setter方法参数
        JCTree.JCVariableDecl param = treeMaker.VarDef(
            treeMaker.Modifiers(Flags.PARAMETER, com.sun.tools.javac.util.List.nil()), //访问修饰符
            variableDeclName, //变量名字
            jcVariableDecl.vartype, //变量类型
            null //变量初始值
        );

        //生成public修饰符
        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);

        //拼接方法名 getXxx
        String getMethodNameStr = "set" + variableDeclName.toString().substring(0, 1).toUpperCase()
            + variableDeclName.toString().substring(1);
        Name getMethodName = names.fromString(getMethodNameStr);

        //生成返回类型标识 void
        JCTree.JCExpression returnMethodType = treeMaker.Type(new Type.JCVoidType());

        //生成方法体
        JCTree.JCBlock body = treeMaker.Block(0, statements.toList());

        //生成泛型参数列表
        com.sun.tools.javac.util.List<JCTree.JCTypeParameter> methodGenericParameterList
            = com.sun.tools.javac.util.List.nil();

        //生成参数值列表
        com.sun.tools.javac.util.List<JCTree.JCVariableDecl> methodParameterList = com.sun.tools.javac.util.List.of(
            param);

        //生成抛出的异常列表
        com.sun.tools.javac.util.List<JCTree.JCExpression> throwExceptionList = com.sun.tools.javac.util.List.nil();

        //生成方法定义AST节点
        return treeMaker.MethodDef(modifiers,  //public
            getMethodName, // 方法名: setXxx
            returnMethodType, //返回类型
            methodGenericParameterList, //泛型参数列表
            methodParameterList, //参数类型列表
            throwExceptionList, //抛出异常列表
            body, //方法体
            null);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //获取标注了MyData注解的元素, 这里实际上只有类元素
        Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(MyData.class);

        for (Element element : set) {
            //获取标注了MyData注解的类的语法树
            JCTree tree = javacTrees.getTree(element);

            //notice: 解决编译错误【java.lang.AssertionError: Value of x -1】
            // 因为treeMaker.pos的值是不会变的(=-1), 所以在遍历是需要实时更新
            treeMaker.pos = tree.pos;

            //遍历语法树(在遇到visitClassDef事件, 也就是访问到类定义节点时去修改语法树节点)
            tree.accept(new TreeTranslator() {
                @Override
                public void visitClassDef(JCClassDecl jcClassDecl) {
                    //获取定义在该类下的所有元素(成员变量, 方法等)
                    jcClassDecl.defs.stream()
                        //过滤出变量类型的元素
                        .filter(o -> o.getKind().equals(Kind.VARIABLE))
                        //强制转换元素为变量类型元素
                        .map(o -> ((JCTree.JCVariableDecl)o))
                        //遍历处理每个变量元素
                        .forEach(o -> {
                            //为类定义节点新增getter方法
                            jcClassDecl.defs = jcClassDecl.defs.prepend(genGetterMethod(o));

                            //为类定义节点新增setter方法
                            jcClassDecl.defs = jcClassDecl.defs.prepend(genSetterMethod(o));
                        });
                    //修改类节点完毕
                    super.visitClassDef(jcClassDecl);
                }
            });
        }

        return true;
    }

}

接下来声明一个User类,如下:

package com.xycode.techlecture.jsr269;

/**
 * @author: xycode
 * @email: lianguang.xy@alibaba-inc.com
 * @date: 2022/7/12
 */
@MyData
public class User {

    private Integer id;

    private String name;

    public User() {
    }

    public static void main(String[] args) {
        User user = new User();
        user.setId(1234);
        user.setName("xycode");
        System.out.println(user.getId());
        System.out.println(user.getName());
    }
}

可以看到User类这里没有创建getter,setter方法,但是main中使用到了,显然正常情况下会编译报错,但是借助JSR 269的插件化注解处理能力,我们可以在编译期间介入AST的生成,从而完成getter,setter的正常调用,编译命令如下:

先编译MyData与MyDataAnnotationProcessor:

再编译User:

可以看出使能够编译成功的,反编译看一下User,如下

可以看出getter,setter方法已经正常生成了,执行如下:

执行也是ok的。

目录
相关文章
|
1月前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
64 7
|
2月前
|
存储 缓存 安全
Java内存模型深度解析:从理论到实践####
【10月更文挑战第21天】 本文深入探讨了Java内存模型(JMM)的核心概念与底层机制,通过剖析其设计原理、内存可见性问题及其解决方案,结合具体代码示例,帮助读者构建对JMM的全面理解。不同于传统的摘要概述,我们将直接以故事化手法引入,让读者在轻松的情境中领略JMM的精髓。 ####
43 6
|
2月前
|
设计模式 Java 开发者
Java中的异常处理:理解与实践
【10月更文挑战第42天】在Java的世界中,异常处理是每个开发者必须面对的挑战。它就像是一场不可预知的风暴,可能会在任何时候突然降临,打乱我们的计划。但是,如果我们能够掌握正确的处理方法,这场风暴也可以变成推动我们前进的力量。本文将带你深入理解Java中的异常处理机制,通过代码示例,我们将一起学习如何捕获、处理和预防异常,让你的程序在面对任何挑战时都能保持稳健和优雅。
|
2月前
|
Arthas 监控 Java
拥抱 OpenTelemetry:阿里云 Java Agent 演进实践
本文介绍了阿里云 Java Agent 4.x 版本在基于 OTel Java Agent 二次开发过程中的实践与思考,并重点从功能、性能、稳定性、兼容性四个方面介绍了所做的工作。同时也介绍了阿里云可观测团队积极参与开源建设取得的丰厚成果。
248 7
拥抱 OpenTelemetry:阿里云 Java Agent 演进实践
|
1月前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
1月前
|
安全 Java 数据库连接
Java中的异常处理:理解与实践
在Java的世界里,异常处理是维护代码健壮性的守门人。本文将带你深入理解Java的异常机制,通过直观的例子展示如何优雅地处理错误和异常。我们将从基本的try-catch结构出发,探索更复杂的finally块、自定义异常类以及throw关键字的使用。文章旨在通过深入浅出的方式,帮助你构建一个更加稳定和可靠的应用程序。
34 5
|
1月前
|
Java 编译器 数据库
Java 中的注解(Annotations):代码中的 “元数据” 魔法
Java注解是代码中的“元数据”标签,不直接参与业务逻辑,但在编译或运行时提供重要信息。本文介绍了注解的基础语法、内置注解的应用场景,以及如何自定义注解和结合AOP技术实现方法执行日志记录,展示了注解在提升代码质量、简化开发流程和增强程序功能方面的强大作用。
82 5
|
2月前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
173 6
|
1月前
|
安全 Java 程序员
Java内存模型的深入理解与实践
本文旨在深入探讨Java内存模型(JMM)的核心概念,包括原子性、可见性和有序性,并通过实例代码分析这些特性在实际编程中的应用。我们将从理论到实践,逐步揭示JMM在多线程编程中的重要性和复杂性,帮助读者构建更加健壮的并发程序。
|
2月前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
71 1