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

目录
相关文章
|
2月前
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
54 0
|
3月前
|
资源调度 安全 Java
Java 大数据在智能教育在线实验室设备管理与实验资源优化配置中的应用实践
本文探讨Java大数据技术在智能教育在线实验室设备管理与资源优化中的应用。通过统一接入异构设备、构建四层实时处理管道及安全防护双体系,显著提升设备利用率与实验效率。某“双一流”高校实践显示,设备利用率从41%升至89%,等待时间缩短78%。该方案降低管理成本,为教育数字化转型提供技术支持。
84 1
|
2月前
|
并行计算 Java API
Java List 集合结合 Java 17 新特性与现代开发实践的深度解析及实战指南 Java List 集合
本文深入解析Java 17中List集合的现代用法,结合函数式编程、Stream API、密封类、模式匹配等新特性,通过实操案例讲解数据处理、并行计算、响应式编程等场景下的高级应用,帮助开发者提升集合操作效率与代码质量。
114 1
|
2月前
|
安全 Java API
Java 17 及以上版本核心特性在现代开发实践中的深度应用与高效实践方法 Java 开发实践
本项目以“学生成绩管理系统”为例,深入实践Java 17+核心特性与现代开发技术。采用Spring Boot 3.1、WebFlux、R2DBC等构建响应式应用,结合Record类、模式匹配、Stream优化等新特性提升代码质量。涵盖容器化部署(Docker)、自动化测试、性能优化及安全加固,全面展示Java最新技术在实际项目中的应用,助力开发者掌握现代化Java开发方法。
97 1
|
2月前
|
SQL 缓存 安全
深度理解 Java 内存模型:从并发基石到实践应用
本文深入解析 Java 内存模型(JMM),涵盖其在并发编程中的核心作用与实践应用。内容包括 JMM 解决的可见性、原子性和有序性问题,线程与内存的交互机制,volatile、synchronized 和 happens-before 等关键机制的使用,以及在单例模式、线程通信等场景中的实战案例。同时,还介绍了常见并发 Bug 的排查与解决方案,帮助开发者写出高效、线程安全的 Java 程序。
123 0
|
2月前
|
存储 搜索推荐 算法
Java 大视界 -- Java 大数据在智慧文旅旅游线路规划与游客流量均衡调控中的应用实践(196)
本实践案例深入探讨了Java大数据技术在智慧文旅中的创新应用,聚焦旅游线路规划与游客流量调控难题。通过整合多源数据、构建用户画像、开发个性化推荐算法及流量预测模型,实现了旅游线路的精准推荐与流量的科学调控。在某旅游城市的落地实践中,游客满意度显著提升,景区流量分布更加均衡,充分展现了Java大数据技术在推动文旅产业智能化升级中的核心价值与广阔前景。
|
2月前
|
存储 Java 大数据
Java 大视界 —— 基于 Java 的大数据隐私保护在金融客户信息管理中的实践与挑战(178)
本文探讨了基于 Java 的大数据隐私保护技术在金融客户信息管理中的应用与挑战。随着金融行业数字化转型加速,客户信息的安全性愈发重要。文章详细分析了数据加密、脱敏、访问控制、区块链及联邦学习等关键技术,并结合实际案例展示了其在金融机构中的应用效果,为金融科技从业者提供了宝贵的实践经验与技术参考。
|
3月前
|
数据采集 机器学习/深度学习 Java
Java 大视界 —— Java 大数据在智慧交通停车场智能管理与车位预测中的应用实践(174)
本文围绕 Java 大数据在智慧交通停车场智能管理与车位预测中的应用展开,深入剖析行业痛点,系统阐述大数据技术的应用架构,结合大型体育中心停车场案例,展示系统实施过程与显著成效,提供极具实操价值的技术方案。
|
3月前
|
Java 数据库连接 API
Java 对象模型现代化实践 基于 Spring Boot 与 MyBatis Plus 的实现方案深度解析
本文介绍了基于Spring Boot与MyBatis-Plus的Java对象模型现代化实践方案。采用Spring Boot 3.1.2作为基础框架,结合MyBatis-Plus 3.5.3.1进行数据访问层实现,使用Lombok简化PO对象,MapStruct处理对象转换。文章详细讲解了数据库设计、PO对象实现、DAO层构建、业务逻辑封装以及DTO/VO转换等核心环节,提供了一个完整的现代化Java对象模型实现案例。通过分层设计和对象转换,实现了业务逻辑与数据访问的解耦,提高了代码的可维护性和扩展性。
133 1
|
3月前
|
安全 Java API
Java 抽象类与接口在 Java17 + 开发中的现代应用实践解析
《Java抽象类与接口核心技术解析》 摘要:本文全面剖析Java抽象类与接口的核心概念与技术差异。抽象类通过模板设计实现代码复用,支持具体方法与状态管理;接口则定义行为规范,实现多态支持。文章详细对比了两者在实例化、方法实现、继承机制等方面的区别,并提供了模板方法模式(抽象类)和策略模式(接口)的典型应用示例。特别指出Java8+新特性为接口带来的灵活性提升,包括默认方法和静态方法。最后给出最佳实践建议:优先使用接口定义行为规范,通过抽象类实现代码复用,合理组合两者构建灵活架构。
66 2

热门文章

最新文章