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后,编译器就可以利用这个树状结构来进行代码优化、代码重构、错误检查等等操作。

相关推荐
MadPrinter26 分钟前
SpringBoot学习日记 Day11:博客系统核心功能深度开发
java·spring boot·后端·学习·spring·mybatis
dasseinzumtode26 分钟前
nestJS 使用ExcelJS 实现数据的excel导出功能
前端·后端·node.js
淦出一番成就29 分钟前
Java反序列化接收多种格式日期-JsonDeserialize
java·后端
Java中文社群32 分钟前
Hutool被卖半年多了,现状是逆袭还是沉寂?
java·后端
程序员蜗牛1 小时前
9个Spring Boot参数验证高阶技巧,第8,9个代码量直接减半!
后端
yeyong1 小时前
咨询kimi关于设计日志告警功能,还是有启发的
后端
库森学长1 小时前
2025年,你不能错过Spring AI,那个汲取了LangChain灵感的家伙!
后端·openai·ai编程
Java水解2 小时前
Spring Boot 启动流程详解
spring boot·后端
学历真的很重要2 小时前
Claude Code Windows 原生版安装指南
人工智能·windows·后端·语言模型·面试·go
转转技术团队2 小时前
让AI成为你的编程助手:如何高效使用Cursor
后端·cursor