关于后端:用了那么久的Lombok你知道它的原理么

48次阅读

共计 6167 个字符,预计需要花费 16 分钟才能阅读完成。

简介:在写 Java 代码的时候,最烦写 setter/getter 办法,自从有了 Lombok 插件不必再写那些办法之后,感觉再也回不去了,那你们是否好奇过 Lombok 是怎么把 setter/getter 办法给你加上去的呢?有的同学说咱们 Java 引入 Lombok 之后会净化依赖包,那咱们可不可以本人写一个工具来代替 Lombok 呢?

作者 | 王再军 (曦峰) 起源 | 阿里开发者公众号序言在写 Java 代码的时候,最烦写 setter/getter 办法,自从有了 Lombok 插件不必再写那些办法之后,感觉再也回不去了,那你们是否好奇过 Lombok 是怎么把 setter/getter 办法给你加上去的呢?有的同学说咱们 Java 引入 Lombok 之后会净化依赖包,那咱们可不可以本人写一个工具来代替 Lombok 呢?知识点 Java 编译过程理解 Lombok 原理理解插入式注解处理器剖析序言提到的问题其实都是同一个问题,就是如何去获取和批改 Java 源代码?要答复这个问题,咱们须要答复这几个问题:Java 编译器是如何解析 Java 源代码的?编译器编译源代码都有哪些步骤?咱们在编译器工作的时候,怎么能力去减少内容或者是进行代码剖析?心愿大家看完本文可能本人写一个繁难的 Lombok 工具。答复如何解析源代码其实从咱们的代码到被编译,中距离了一个数据结构,叫做 AST(形象树)。具体的模式,能够查看上面的图片。左边的便是 AST 的数据结构了。

 代码编译都有哪些步骤整个编译过程大抵如下:

  图片来自 openjdk 1、初始化插入注解处理器 2、解析与填充符号表过程 a. 词法剖析、语法分析。将源代码的字符流转变为标记汇合,结构出形象语法树。b. 填充符号表。产生符号地址和符号信息。3、插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段。前面我会给大家带来两个此方面的实用实战例子。4、剖析与字节码生成过程 a. 标注查看。对语法的动态信息查看。b. 数据流及控制流剖析。对程序动静运行过程进行查看。c. 解语法糖。将简化代码编写的语法糖还原为原有的模式。d. 字节码生成。将后面各个步骤所生成的信息转化成为字节码。咱们晓得了下面的实践之后,接下来咱们进行实战。带着大家一起去批改 AST(形象树)。增加本人的代码。实战如何本人实现一个主动增加 Setter/Getter 的工具首先,咱们创立一个本人的注解。@Retention(RetentionPolicy.SOURCE) // 注解只在源码中保留
@Target(ElementType.TYPE) // 用于润饰类
public @interface MySetterGetter {
}
创立一个须要生成 setter/getter 办法的实体类 @MySetterGetter // 打上咱们的注解
public class Test {

private String wzj;

}
接下来就来看一看如何来生成咱们想要的字符串。整体代码如下:@SupportedAnnotationTypes(“com.study.practice.nameChecker.MySetterGetter”)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MySetterGetterProcessor extends AbstractProcessor {

// 次要是输入信息
private Messager messager;
private JavacTrees javacTrees;

private TreeMaker treeMaker;
private Names names;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);
    this.messager = processingEnv.getMessager();
    this.javacTrees = 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> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(MySetterGetter.class);
    elementsAnnotatedWith.forEach(element -> {
        // 失去类的形象树结构
        JCTree tree = javacTrees.getTree(element);
        // 遍历类,对类进行批改
        tree.accept(new TreeTranslator(){
            @Override
            public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
                // 在形象树中找出所有的变量
                for(JCTree jcTree: jcClassDecl.defs){if (jcTree.getKind().equals(Tree.Kind.VARIABLE)){JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl)jcTree;
                        jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
                    }
                }
                
                // 对于变量进行生成办法的操作
                for (JCTree.JCVariableDecl jcVariableDecl : jcVariableDeclList) {messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + "has been processed");
                    jcClassDecl.defs = jcClassDecl.defs.prepend(makeSetterMethodDecl(jcVariableDecl));

                    jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
                }


    // 生成返回对象
    JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType());

    return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewSetterMethodName(jcVariableDecl.getName()), methodType, List.nil(), parameters, List.nil(), block, null);
}
/**
 * 生成 getter 办法
 * @param jcVariableDecl
 * @return
 */
private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl){ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
    // 生成表达式
    JCTree.JCReturn aReturn = treeMaker.Return(treeMaker.Ident(jcVariableDecl.getName()));
    statements.append(aReturn);
    JCTree.JCBlock block = treeMaker.Block(0, statements.toList());
    // 无入参
    // 生成返回对象
    JCTree.JCExpression returnType = treeMaker.Type(jcVariableDecl.getType().type);
    return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewGetterMethodName(jcVariableDecl.getName()), returnType, List.nil(), List.nil(), List.nil(), block, null);
}
/**
 * 拼装 Setter 办法名称字符串
 * @param name
 * @return
 */
private Name getNewSetterMethodName(Name name) {String s = name.toString();
    return names.fromString("set" + s.substring(0,1).toUpperCase() + s.substring(1, name.length()));
}
/**
 * 拼装 Getter 办法名称的字符串
 * @param name
 * @return
 */
private Name getNewGetterMethodName(Name name) {String s = name.toString();
    return names.fromString("get" + s.substring(0,1).toUpperCase() + s.substring(1, name.length()));
}
/**
 * 生成表达式
 * @param lhs
 * @param rhs
 * @return
 */
private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, JCTree.JCExpression rhs) {
    return treeMaker.Exec(treeMaker.Assign(lhs, rhs)
    );
}

}代码有点多,咱们逐个拆解阐明:上面这是整个代码构造的脑图,前面的解说会基于这个程序。

 a. 注解 @SupportedAnnotationTypes 示意咱们须要监听的注解,比方咱们之前定义的 @MySetterGetter。@SupportedSourceVersion 示意咱们想要对什么版本的 Java 源代码进行解决。b. 父类 AbstractProcessor 是本次的外围类,编译器在编译的时候会扫描此类的子类。其中有一个子类必须实现的外围办法 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv),此办法如果是返回为 true 就阐明编译的那个类形象树的构造又变动,须要从新进行词法剖析和语法分析(能够查看下面提到的那个编译流程图)。如果返回的是 false 就阐明没有变动。c. process 办法次要的操作逻辑是:1、拿到所有被咱们 MySetterGetter 标注的类。2、遍历所有的类,生成类的形象树结构。3、对类进行操作:a. 找到类中所有的变量。b. 对变量进行生成 Set 和 Get 办法。4、返回 true,阐明类构造变了,须要从新解析。如果是 false 阐明没有变,不必从新解析。d. 操作 JCTree 树次要是在操作形象树,能够查看文末附件中的文章进行学习。e. 办法名称拼接这一块儿和字符串拼接没啥区别,用过反射的同学应该也都分明这个操作了。到此为止,咱们就曾经介绍完了 Lombok 的原理。怎么样是不是很简略。接下来,就让咱们把它运行起来,投入到实战之中。f. 运行最初来看一下如何正确的运行这个咱们写的工具。1. 环境我的零碎环境是 macOs Monterey;java 版本是 openjdk version “1.8.0_302”
OpenJDK Runtime Environment (Temurin)(build 1.8.0_302-b08)
OpenJDK 64-Bit Server VM (Temurin)(build 25.302-b08, mixed mode)
2. 编译 processor 在你寄存 MySetterGetter 和 MySetterGetterProcessor 两个类的目录下进行编译。javac -cp $JAVA_HOME/lib/tools.jar MySetterGetter.java MySetterGetterProcessor.java
执行胜利后会呈现这三个 class 文件。

 3. 申明插入式注解处理器 

 在你的工程的 resources 上面创立一个包,名称为:META-INFO.services 而后创立一个文件,名称为:javax.annotation.processing.Processor 将你的注解处理器的地址填入,我的配置是这样的:com.study.practice.nameChecker.MySetterGetterProcessor4. 用咱们的工具去编译指标类比方咱们本次是要编译那个 test.java。它的内容再回顾一下:@MySetterGetter // 打上咱们的注解
public class Test {

private String wzj;

}
而后咱们就去编译它(留神类后面的门路。这个你们得换成本人的工程目录。)javac -processor com.study.practice.nameChecker.MySetterGetterProcessor com/study/practice/nameChecker/Test.java
执行之后如果没有批改我的代码的话会打印这几个字符串:process 1
process 2
注: wzj has been processed
process 1
最初会生成 Test.class 文件。

 5. 成绩最初的 class 文件解析进去就是这个样子的。如下图所示:

 看到 Setter/Getter 办法就阐明咱们曾经功败垂成了!是不是很简略。到此为止,咱们就学会了如何本人写一个属于本人的繁难 Lombok 的插件了。附件 treemarker 的介绍:http://www.docjar.com/docs/ap… ModelScope 开源模型社区评测征集令 ModelScope 开源模型社区评测专场重磅来袭,公布你的评测,收费应用模型库搭建属于你的利用,有机会取得 AirPods 和阿里云定制礼品,更有多重福利,点击这里查看流动详情。原文链接:https://click.aliyun.com/m/10… 本文为阿里云原创内容,未经容许不得转载。

正文完
 0