• 从Lombok到JSR-269


    前言

    Lombok的出现帮助开发人员在开发工程中消除了大部分冗余代码:繁琐的get、set方法甚至建造者模式。
    Lombok的实现方式是什么呢?
    新建一个测试类使用Lombok的Getter和Setter注解,通过IDEA进行编译

    import lombok.Getter;
    import lombok.Setter;
    
    @Getter
    @Setter
    public class UserInfo {
        private String userId;
    
        private String userName;
    }
    

    打开编译后生成的UserInfo.class文件

    发现已经生成了get、set方法,由此可以推断出Lombok是在编译期为代码进行了增强,那么在编译期进行增强是如何实现的?

    编译阶段

    在JDK6提出并通过了JSR-269提案,提案通过了一组被称为“插入式注解处理器”的标准API,可以提前至编译期对代码中的特定注解进行处理, 从而影响到编译器的工作过程。
    对于底层的一些实现,普遍会认为实现是像虚拟机一样使用C++实现,对于Java程序员来说并不是特别友好。但是Javac编译器是使用Java实现的更容易上手。
    Javac的编译过程大致分为几步

    1. 准备过程:初始化插入式注解处理器
    2. 解析与填充符号表过程 词法、语法分析构建抽象语法树(AST)
    3. 插入式注解处理器的注解处理过程
    4. 分析与字节码生成过程
      语法树变动后会再次解析与填充符号表,语法树没有变动时编译器就不会再对源码字符流操作,而是基于抽象语法树

      综上所述想实现Lombok的效果只需要遵守JSR-269在编译期对AST进行操作即可实现
      当然不止有Lombok通过这种方式实现,例如FindBug、MapStruct等也通过这种方式实现

    实现

    JCTree

    JCTree是AST元素的基类,想实现效果只需要添加JCTree节点即可
    JCTree是一个抽象类部分实现

    从类名可以猜到是什么节点使用的这里挑几个常用的解释
    JCStatement 声明语法树节点
    JCBlock 语法块
    JCReturn:return语句语法树节点
    JCClassDecl:类定义语法树节点
    JCVariableDecl:字段/变量定义语法树节点
    JCMethodDecl:方法定义语法树节点
    JCModifiers:访问标志语法树节点
    JCExpression:表达式语法树节点,常见的子类如下
    JCAssign:赋值语句语法树节点
    JCIdent:标识符语法树节点 例如this

    TreeMaker

    主要用于生成语法树节点

    代码

    首先需要注解类,标明作用的范围和作用的时期,两个类分别对应Lombok的Getter、Setter

    @Target({ElementType.TYPE}) //加在类上的注解
    @Retention(RetentionPolicy.SOURCE) //作用于编译期
    public @interface Getter {
    }
    @Target({ElementType.TYPE}) //加在类上的注解
    @Retention(RetentionPolicy.SOURCE) //作用于编译期
    public @interface Setter {
    }
    

    新建抽象注解处理器需要继承AbstractProcessor,这里使用模板方法模式

    public abstract class MyAbstractProcessor extends AbstractProcessor {
        //语法树
        protected JavacTrees trees;
    
        //构建语法树节点
        protected TreeMaker treeMaker;
    
        //创建标识符的对象
        protected Names names;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            this.trees = JavacTrees.instance(processingEnv);
            Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
            this.treeMaker = TreeMaker.instance(context);
            this.names = Names.instance(context);
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            //获取注解标识的类
            Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(getAnnotation());
            //拿到语法树
            set.stream().map(element -> trees.getTree(element)).forEach(jcTree -> jcTree.accept(new TreeTranslator() {
                //拿到类定义
                @Override
                public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                    List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
                    //拿到所有成员变量
                    for (JCTree tree : jcClassDecl.defs) {
                        if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
                            JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
                            jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
                        }
                    }
    
                    jcVariableDeclList.forEach(jcVariableDecl -> {
                        jcClassDecl.defs = jcClassDecl.defs.prepend(makeMethodDecl(jcVariableDecl));
                    });
                    super.visitClassDef(jcClassDecl);
                }
            }));
            return true;
        }
    
        /**
         * 创建方法
         * @param jcVariableDecl
         * @return
         */
        public abstract JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl);
    
        /**
         * 获取何种注解
         * @return
         */
        public abstract Class<? extends Annotation> getAnnotation();
    }
    

    用于处理Setter注解 继承MyAbstractProcessor

    @SupportedAnnotationTypes("com.ingxx.processor.Setter") //注解处理器作用于哪个注解 也可以重写getSupportedAnnotationTypes
    @SupportedSourceVersion(SourceVersion.RELEASE_8) //可以处理什么版本 也可以重写getSupportedSourceVersion
    public class SetterProcessor extends MyAbstractProcessor {
    
        @Override
        public JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
            ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
            //生成函数体 this.name = name;
            statements.append(treeMaker.Exec(treeMaker.Assign(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()),treeMaker.Ident(jcVariableDecl.getName()))));
            JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
            //生成方法
            return treeMaker.MethodDef(
                    treeMaker.Modifiers(Flags.PUBLIC), //访问标志
                    getNewMethodName(jcVariableDecl.getName()), //名字
                    treeMaker.TypeIdent(TypeTag.VOID), //返回类型
                    List.nil(), //泛型形参列表
                    List.of(getParameters(jcVariableDecl)), //参数列表
                    List.nil(), //异常列表
                    body, //方法体
                    null //默认方法(可能是interface中的那个default)
            );
        }
    
        @Override
        public Class<? extends Annotation> getAnnotation() {
            return Setter.class;
        }
    
        private Name getNewMethodName(Name name) {
            String fieldName = name.toString();
            return names.fromString("set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, name.length()));
        }
    
        private JCTree.JCVariableDecl getParameters(JCTree.JCVariableDecl prototypeJCVariable) {
            return treeMaker.VarDef(
                    treeMaker.Modifiers(Flags.PARAMETER), //访问标志
                    prototypeJCVariable.name, //名字
                    prototypeJCVariable.vartype, //类型
                    null //初始化语句
            );
        }
    }
    

    用于处理Getter注解 继承MyAbstractProcessor

    @SupportedAnnotationTypes("com.ingxx.processor.Getter") //注解处理器作用于哪个注解 也可以重写getSupportedAnnotationTypes
    @SupportedSourceVersion(SourceVersion.RELEASE_8) //可以处理什么版本 也可以重写getSupportedSourceVersion
    public class GetterProcessor extends MyAbstractProcessor {
    
        @Override
        public JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
            ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
            //生成函数体 return this.字段名
            statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
            JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
            //生成方法
            return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), body, null);
        }
    
        @Override
        public Class<? extends Annotation> getAnnotation() {
            return Getter.class;
        }
    
        private Name getNewMethodName(Name name) {
            String fieldName = name.toString();
            return names.fromString("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, name.length()));
        }
    }
    

    编译现有类

    javac -cp $JAVA_HOME/lib/tools.jar *.java -d . 
    

    新建一个测试类 IDEA中由于找不到get set方法会报错,可以忽略

    @Getter
    @Setter
    public class UserInfo {
        private String userId;
    
        private String userName;
    
        public static void main(String[] args) {
            UserInfo userInfo = new UserInfo();
            userInfo.setUserId("001");
            userInfo.setUserName("得物");
            System.out.println("id = "+userInfo.getUserId()+" name = "+userInfo.getUserName());
        }
    }
    

    接着编译

    //多个处理器用逗号分隔
    javac -processor com.ingxx.processor.GetterProcessor,com.ingxx.processor.SetterProcessor UserInfo.java -d .
    

    查看编译后的文件发现已经生成了get、set方法

  • 相关阅读:
    poj 3026 Borg Maze
    poj2828 Buy Tickets
    poj3264 Balanced Lineup
    高精度运算
    poj1035 Spell checker
    poj2318 TOYS 点积叉积理解
    求两直线交点的一般做法
    C语言I博客作业05
    C语言I博客作业04
    C语言I博客作业07
  • 原文地址:https://www.cnblogs.com/ingxx/p/14840977.html
Copyright © 2020-2023  润新知