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的。

目录
相关文章
|
12天前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
18 2
|
8天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
5天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
9天前
|
SQL Java 数据库连接
从理论到实践:Hibernate与JPA在Java项目中的实际应用
本文介绍了Java持久层框架Hibernate和JPA的基本概念及其在具体项目中的应用。通过一个在线书店系统的实例,展示了如何使用@Entity注解定义实体类、通过Spring Data JPA定义仓库接口、在服务层调用方法进行数据库操作,以及使用JPQL编写自定义查询和管理事务。这些技术不仅简化了数据库操作,还显著提升了开发效率。
21 3
|
8天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
21天前
|
Java 开发者 Spring
[Java]自定义注解
本文介绍了Java中的四个元注解(@Target、@Retention、@Documented、@Inherited)及其使用方法,并详细讲解了自定义注解的定义和使用细节。文章还提到了Spring框架中的@AliasFor注解,通过示例帮助读者更好地理解和应用这些注解。文中强调了注解的生命周期、继承性和文档化特性,适合初学者和进阶开发者参考。
43 14
|
21天前
|
前端开发 Java
[Java]讲解@CallerSensitive注解
本文介绍了 `@CallerSensitive` 注解及其作用,通过 `Reflection.getCallerClass()` 方法返回调用方的 Class 对象。文章还详细解释了如何通过配置 VM Options 使自定义类被启动类加载器加载,以识别该注解。涉及的 VM Options 包括 `-Xbootclasspath`、`-Xbootclasspath/a` 和 `-Xbootclasspath/p`。最后,推荐了几篇关于 ClassLoader 的详细文章,供读者进一步学习。
29 12
|
21天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
59 10
|
14天前
|
Java 程序员 数据库连接
Java中的异常处理:理解与实践
【10月更文挑战第29天】在Java编程的世界里,异常像是不请自来的客人,它们可能在任何时候闯入我们的程序宴会。了解如何妥善处理这些意外访客,不仅能够保持我们程序的优雅和稳健,还能确保它不会因为一个小小的失误而全盘崩溃。本文将通过浅显易懂的方式,带领读者深入异常处理的核心概念,并通过实际示例展现如何在Java代码中实现有效的异常管理策略。
|
18天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
22 3