Lombok技术的原理是什么

相信大家在工作或者学习中都会有或多或少地使用过lombok插件来自动生成Bean的getter和setter方法,从而提升我们的开发效率。

可能有同学也会有所疑惑,到底lombok的底层原理是如何实现的呢?今天让我们来一起研究下,尝试手写一款简单版本的lombok。

我们实现一个简易版的 Lombok 自定义一个 Getter 方法,我们的实现步骤是:

  1. 自定义一个注解标签接口,并实现一个自定义的注解处理器;
  2. 利用 tools.jar 的 javac api 处理 AST (抽象语法树)
  3. 使用自定义的注解处理器编译代码。

自定义手写Getter注解

首先需要自定义一个注解:

java 复制代码
package com.sise.lombok.core;

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

/**
 * @Author idea
 * @Date: Created in 16:52 2024/3/23
 * @Description
 */
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface MyGetter {


}

自定义Processor

接下来我们需要自定义一个Processor对象:

java 复制代码
package com.sise.lombok.core;

import com.sun.source.tree.Tree;
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.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.*;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;


/**
 * @Author idea
 * @Date: Created in 16:53 2024/3/23
 * @Description
 */
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.sise.lombok.core.MyGetter")
public class MyGetterProcessor extends AbstractProcessor {

    private Messager messager; // 编译时期输入日志的
    private JavacTrees javacTrees; // 提供了待处理的抽象语法树
    private TreeMaker treeMaker; // 封装了创建AST节点的一些方法
    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(MyGetter.class);
        elementsAnnotatedWith.forEach(e -> {
            JCTree tree = javacTrees.getTree(e);
            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);
                        }
                    }
                    // 对于变量进行生成方法的操作
                    jcVariableDeclList.forEach(jcVariableDecl -> {
                        messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");
                        jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
                    });
                    super.visitClassDef(jcClassDecl);
                }
            });
        });
        return true;
    }

    private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
        // 生成表达式 例如 this.a = a;
        JCTree.JCExpressionStatement aThis = makeAssignment(treeMaker.Select(treeMaker.Ident(
                names.fromString("this")), jcVariableDecl.getName()), treeMaker.Ident(jcVariableDecl.getName()));
        statements.append(aThis);
        JCTree.JCBlock block = treeMaker.Block(0, statements.toList());

        // 生成入参
        JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),
                jcVariableDecl.getName(), jcVariableDecl.vartype, null);
        List<JCTree.JCVariableDecl> parameters = List.of(param);

        // 生成返回对象
        JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType());
        return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),
                getNewMethodName(jcVariableDecl.getName()), methodType, List.nil(),
                parameters, List.nil(), block, null);

    }

    private Name getNewMethodName(Name name) {
        String s = name.toString();
        return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
    }

    private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, JCTree.JCExpression rhs) {
        return treeMaker.Exec(
                treeMaker.Assign(
                        lhs,
                        rhs
                )
        );
    }
}

自定义测试Bean

最后我们来定义一个Bean对象即可:

java 复制代码
package com.sise.lombok.core;

/**
 * @Author idea
 * @Date: Created in 16:55 2024/3/23
 * @Description
 */
@MyGetter
public class User {

    private String name;
}

此时我们的目录结构如下图所示:

接下来我们进入到class文件的同级目录,执行以下命令:

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

注意最后的那个点是指当前目录位置的Class文件。

接下来,我们需要做的是制定processor,再去执行User.class的编译,命令如下:

  • javac -processor com.sise.lombok.core.MyGetterProcessor User.java

这样我们就能看到一个新的User.class文件的出现了,打开User.class你会看到有对应的getter和setter方法存在。

至此,我们已经了解了如何基于自定义的Processor在进行字节码生成的时候做增强处理。

Processor的处理原理是什么

在编译期阶段,当 Java 源码被抽象成语法树 (AST) 之后,Lombok 会根据自己的注解处理器动态的修改 AST,增加新的代码 (节点),在这一切执行之后,再通过分析生成了最终的字节码 (.class) 文件,这就是 Lombok 的执行原理。

AST是什么

Java AST(Abstract Syntax Tree,抽象语法树)是指在Java代码编译过程中,将源代码转化为一棵树状结构表示的中间表示形式。这个树状结构由多个节点组成,每个节点表示代码中的一个语法单元,比如一个类、一个方法、一个表达式、一个变量声明等等。

Java AST是在编译器分析Java源代码的过程中生成的,它包含了源代码的所有信息,并且能够准确地描述代码的结构和语法。在编译过程中,编译器会对源代码进行词法分析、语法分析和语义分析,然后将分析结果生成Java AST。生成AST后,编译器就可以利用这个树状结构来进行代码优化、代码重构、错误检查等等操作。

相关推荐
瓜牛_gn8 分钟前
依赖注入注解
java·后端·spring
Estar.Lee25 分钟前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
喜欢猪猪27 分钟前
Django:从入门到精通
后端·python·django
一个小坑货27 分钟前
Cargo Rust 的包管理器
开发语言·后端·rust
bluebonnet2731 分钟前
【Rust练习】22.HashMap
开发语言·后端·rust
uhakadotcom1 小时前
如何实现一个基于CLI终端的AI 聊天机器人?
后端
Iced_Sheep1 小时前
干掉 if else 之策略模式
后端·设计模式
XINGTECODE2 小时前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
程序猿进阶2 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺2 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端