找回密码
 会员注册
查看: 20|回复: 0

Lombok技术揭秘_自动生成带代码的幕后机制

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
64107
发表于 2024-10-12 20:59:48 | 显示全部楼层 |阅读模式
Lombok技术揭秘 _ 自动生成带代码的幕后机制 374 1. Lombok简介1.1 Lombok是什么引入官方解释roject Lombok 是一个 JAVA 库,它可以自动插入编辑器和构建工具,为您的 JAVA 锦上添花。再也不要写另一个 getter/setter 或 equals 等方法,只要有一个注注解,你的类就有一个功能齐全的生成器,自动记录变量,等等。1.2 Lombck相关注解功能介绍注解功能@Getter@Setter1.能够作用在字段或类上生成 get/set 方法;2.value 设置方法访问级别默认 public ;@NoArgsConstructor@AllArgsConstructor@RequiredArgsConstructor1.生成无参/全参/( final @Nonnull 参数)的构造方法作用于类。2. staticName 有值时,会私有当前构造方法,提供指定名称 public 静态构造方法。3. access 构造器的访问权限,默认public 。@ToString1.生成类的 toString 方法,作用在类上。2.属性 includeFieldNames 是否打印字段名称。3.属性 exclude 排除不希望生成在 toString 中的字段。4.属性 of 指定生成在 toString 的字段。5.属性 callSuper 是否生成父类属性在toString。@Data等同于生成 @ToString、@EqualsAndHashCode、@Getter、@Setter、 @RequiredArgsConstrutor 注解。@Builder用在类、构造器、方法上提供建造者模式的构建器类,用于创建对象。@Slf4j在类中生成一个日志记录器( Logger )的字段。@SneakyThrows方法上自动添加异常处理代码,以使编译器不会抛出异常未处理的警告。@Value用于创建不可变的值对象( Value Object )类.即所有属性都是final的,并且只有 getter 方法,没有 setter 方法。@Accessor为属性生成自定义的访问方法,可以控制方法的名称、修饰符、参数等。@Cleanup为需要关闭的资源自动调用 close 方法,避免资源泄漏。2. Lombok原理介绍2.1 Java类文件编译过程首先,我们知道 Lombok 功能是作用在类编译时期,那我们来看下一个类编译的过程。定义一个 PersonDTO.Java 类public class ersonDTO {  //姓名  private String name;  }Javac PersonDTO.Java 对源代码进行解析转化,会生成一棵抽象语法树( AST );运行过程中会调用实现了 JSR 269 注解处理器,下面介绍;JSR 实现可处理自定义逻辑,包括可修改编译后的抽象语法树(AST);Javac 使用修改后的抽象语法树(AST)生成字节码文件;过程如下图:AST 是抽象语法树(Abstract Syntax Tree) 的缩写,是 JAVA 源代码展示的一种树状结构它将代码的结构和语法元素映射到树节点上,使得程序可以在编译、分析和转换过程中更容易地操作和理解。有兴趣可以学习 JavaParser 源码, 了解将 Java 源代码解析生成成一个抽象语法树( AST ),这个树形结构表示了代码的语法结构包括类、方法、变量、语句等等过程。github地址:https://github.com/javaparser/javaparser.如: PersonDTO.Java 在 idea 中使用可视化工具展示文件 AST 树2.2 JSR 269介绍首先 JSR 269全称" Pluggable Annotation Processing API ",是 JAVA 平台的一项规范,也被称之为注解处理器 API 。在Java6引入,用于在编译时处理注解,目标是提供更丰富的编译时元数据处理能力,以增强Java编译器的功能。这个规范允许开发人员创建自定义的注解处理器,这些处理器可以在编译时检查、分析和生成Java代码。应用框架:Servlet、JAX-RS(RESTful Web服务)JSR 269 来生成用于处理 HTTP 请求的代码。Spring Boot 项目中以处理各种自定义注解,如 @Controller、@Service、@Repository 等。这些注解可以用于自动化配置、依赖注入等方面。Hibernate 它使用 JSR 269 来处理 JPA 注解,并生成与数据库交互的代码。Lombok 是一个 JAVA 库,它通过注解处理器生成常见的 JAVA 代码,如 getter、setter、equals、hashCode 等,以简化开发工作。MapStruct 是一个用于对象映射的 JAVA 库,它使用 JSR 269 来生成类型安全的映射代码,帮助开发人员将一个对象映射到另一个对象。如何实现自定义注解注解处理器:1.声明自定义注解;如 Lombok 下的 @Data,@Getter,@Setter等。2.实现 Process接口,或者继承 AbstractProcessor 复写 process 方法,处理自定义注解逻辑。import javax.annotation.processing.*;import javax.lang.model.SourceVersion;import javax.lang.model.element.*;import java.util.Set;@SupportedAnnotationTypes("com.example.MyAnnotation") // 自定义注解@SupportedSourceVersion(SourceVersion.RELEASE_8) //支持的Java版本public class MyAnnotationProcessor extends AbstractProcessor {  @Override  public boolean process(Set annotations, RoundEnvironment roundEnv)  {    // 在这里处理自定义注解,生成代码或执行其他任务    return true;  }}3.注册注解处理器两种方式:Resource 文件:项目 META-INF/services 创建 javax.annotation.processing.Processor 文件,自定义注解处理器的全类名写到此文件中。通过谷歌工具包 auto-service ,可自动生成以上配置文件。  com.google.auto.service  auto-service  1.0.1 @AutoService(Processor.class) //谷歌工具包方式:注册注解处理器@SupportedAnnotationTypes("com.example.MyAnnotation") // 自定义注解@SupportedSourceVersion(SourceVersion.RELEASE_8) //支持的Java版本public class MyAnnotationProcessor extends AbstractProcessor {  @Override  public boolean process(Set annotations, RoundEnvironment roundEnv)  {    // 在这里处理自定义注解,生成代码或执行其他任务    return true;  }}2.3 Lombok 实现原理1. Lombok 实际就是结合注解处理器和 AST 技术, Lombok 实现的注解处理器会遍历 AST ,查找与 Lombok 注解相关的元素,根据注解的要求生成新的代码。2.编译前后的 AST 语法树对比加入@Getter注解编译后3. Lombok 注解处理器,采用 Resource 方式注册编译注解处理器注解处理器 AnnotationProcessor 源码:class AnnotationProcessorHider { public static class AstModificationNotifierData {   public volatile static boolean lombokInvoked = false; }  public static class AnnotationProcessor extends AbstractProcessor {  // 获取支持的注解类型  @Override  public Set getSupportedOptions() {   return instance.getSupportedOptions();  }  // 获取支持的注解类型  @Override  public Set getSupportedAnnotationTypes() {   return instance.getSupportedAnnotationTypes();  }  // 支持的JDK版本  @Override  public SourceVersion getSupportedSourceVersion() {   return instance.getSupportedSourceVersion();  }  //初始化环境  @Override  public void init(ProcessingEnvironment processingEnv) {   disableJava9SillyWarning();   AstModificationNotifierData.lombokInvoked = true;   instance.init(processingEnv);   super.init(processingEnv);  }  // 处理自定义注解逻辑  @Override  public boolean process(Set annotations, RoundEnvironment    roundEnv) {   return instance.process(annotations, roundEnv);  } }自定义注解处理器 Handler : 在 Jar 包的 lombok.javac.handlers下,每个注解处理对应一个 Handler. 如 HadlerGetter.java 操作 AST 树生成 getter 方法.2.4手动实现一个 @Getter 功能2.4.1.创建 maven 工程 demo 包含两个子模块 getter/getter-use2.4.2. getter 工程pom文件:        demo    com.example    0.0.1-SNAPSHOT    4.0.0  getter                com.sun      tools      1.6.0      system      ${java.home}/../lib/tools.jar                  com.google.auto.service      auto-service      1.0.1      自定义 GetterTest 注解import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * @author zcy1 */@Retention(RetentionPolicy.SOURCE)@Target(ElementType.TYPE)public @interface GetterTest {}GetterTest 编译注解处理器 GetterProcessorimport com.google.auto.service.AutoService;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 zcy1 */@AutoService(Processor.class) //谷歌工具包方式:注册注解处理器@SupportedSourceVersion(SourceVersion.RELEASE_8)@SupportedAnnotationTypes("GetterTest")public class GetterProcessor extends AbstractProcessor {  /**   * 用于在编译器打印消息的组件   */  private Messager messager;    /**   * 提供待处理抽象语法树   */  private JavacTrees trees;    /**   * 用来构造语法树节点   */  private TreeMaker treeMaker;    /**   * 用于创建标识符的对象   */  private Names names;    /**   * 获取一些注解处理器执行处理逻辑时一些关键对象   * @param processingEnv 处理环境   */  @Override  public synchronized void init(ProcessingEnvironment processingEnv) {    super.init(processingEnv);    this.messager = processingEnv.getMessager();    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 annotations, RoundEnvironment roundEnv)  {    // 获取自定义GetterTest注解的类    Set elementsAnnotatedWith =    roundEnv.getElementsAnnotatedWith(GetterTest.class);    elementsAnnotatedWith.forEach(e -> {      JCTree tree = trees.getTree(e);        tree.accept(new TreeTranslator() {          @Override          public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {            List 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);                 }              }              // 对于变量进行生成getter方法的操作              jcVariableDeclList.forEach(jcVariableDecl -> {                messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + "          has been processed");                  jcClassDecl.defs =           jcClassDecl.defs.prepend(createGetterMethod(jcVariableDecl));              });              super.visitClassDef(jcClassDecl);              }          });      });      return true;  }    private JCTree.JCMethodDecl createGetterMethod(JCTree.JCVariableDecl jcVariableDecl) {    ListBuffer statements = new ListBuffer();       // 生成表达式 this.name = name    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());    // 生成入参 (String name)    JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),            jcVariableDecl.getName(), jcVariableDecl.vartype, null);    List parameters = List.of(param);    // 生成返回对象 void    JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType());       //生成方法名 getName    Name methodName = getMethodName(jcVariableDecl.getName());    // 返回语法树对象  return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),methodName, methodType,     List.nil(),parameters, List.nil(), block, null);    }    /**   * 驼峰方法名   */  private Name getMethodName(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));  }}2.4.4 getter-use工程pom 文件 引入getter工程     demo    com.example    0.0.1-SNAPSHOT      4.0.0    getter-use          com.example    getter    0.0.1-SNAPSHOT      ersonDTO 使用@GetterTest@GetterTestpublic class ersonDTO {  private String name;  private Integer age;}2.5.5 项目进行编译getter 模块下自动生成注册器getter-use 模块下 PersonDTO.class 可见生成了对应属性的 get 方法3.总结本文通过以上对 Lombok 相关介绍,通过对 JAVA 文件编译过程分析和 JSR269 实现的方式, 基于这个规范然后引申出 Lombok 实现原理过程介绍,以及手动实现 getter 案例,想必我们对 Lombok 原理也有了相应的了解。虽然 Lombok 提供了许多便利,由于生成的代码不在源文件中可见,就会导致代码的可读性和维护性较差。在工作中 Lombok 使用时注意闭坑:问题解决@Data 和 @Builder 一起使用时,无参构造方法会被干掉手动加上注解: @AllArgsConstructor、@NoArgsConstructor。@Builder 导致类属性默认值无效。有默认值属性上加注解 : @lombok.Builder.Default。@Data 生成的 toString 方法,默认能不输出父类属性子类添加: @ToString(callSuper = true)。参考文献Lombok 官网地址: https://projectlombok.orgJavaParser 源码地址: https://github.com/javaparser/javaparserJAVA 抽象语法树 AST 浅析与使用: https://www.freesion.com/article/4068581927/
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-26 12:34 , Processed in 0.366003 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表