前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Lombok 原理探析

Lombok 原理探析

作者头像
出其东门
发布2022-03-29 09:17:04
2640
发布2022-03-29 09:17:04
举报
文章被收录于专栏:01二进制01二进制

前言

对于一个 Java 开发者来说,Lombok 应该是使用最多的插件之一了,他提供了一系列注解来帮助我们减轻对重复代码的编写,例如实体类中大量的 setter,getter 方法,各种 IO 流等资源的关闭、try…catch…finally 模版等,虽然可以通过 IDE 的快捷帮我们生成这些方法,但这些冗长的代码仍会影响代码的简洁性与可阅读性。如今,随着使用者数量越来越多,Lombok 甚至成为 IDEA 的内置插件了(2020.3 版本+),可见其影响力。

但不知道使用 Lombok 的你,是否思考过这种自动生成代码究竟是什么原理呢?🤔 这些代码又是怎么产生的呢?而这就是本文要展开介绍的内容了。

Lombok 的安装与使用网络上相关的介绍已经很多了,这里就不多说了,自行查阅相关资料即可。

注解解析的两种方式

关于注解,我在之前的文章里有过详细的介绍,在解释 Lombok 的原理之前,推荐你先阅读 👉《给编译器看的注释——「注解」》 这里主要回顾一下 Lombok 注解的解析方式。

运行时解析

这是最常见的注解解析的方式,运行时能够解析的注解,必须将@Retention设置为RUNTIME,这样就可以通过反射拿到该注解。例如使用最多的@RequestMapping注解就是一个运行时注解。

代码语言:javascript
复制
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String name() default "";

    @AliasFor("path")
    String[] value() default {};

    @AliasFor("value")
    String[] path() default {};

    RequestMethod[] method() default {};

    String[] params() default {};

    String[] headers() default {};

    String[] consumes() default {};

    String[] produces() default {};
}

运行时注解的解析原理也很简单,在 java.lang,reflect 反射包中提供了一个接口 AnnotatedElement,该接口定义了获取注解信息的几个方法,Class、Constructor、Field、Method、Package 等都实现了该接口,对反射熟悉的朋友应该都会很熟悉这种解析方式。相关的例子在之前的文章中有介绍过,这里不赘述了。那 Lombok 的注解也是这种原理吗?翻开源码,我们可以看到@Data这个接口 RetentionPolicy 是SOURCE级别的,也就是说,在代码编译的时候,相关的注解信息就已经丢掉了,并不会被加载进 JVM 里,那么为什么我们又会在 Compile 代码的时候看见那些 get/set 方法呢?这就不得不说另一种注解解析方式了——编译时解析

代码语言:javascript
复制
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
    String staticConstructor() default "";
}

编译时解析

Javac 编译器的编译过程大致可分为 1 个准备过程 3 个处理过程 :

1.初始化插入式注解处理器2.解析与填充符号表;3.插入式注解处理器的注解处理;4.分析与字节码生成。

这 3 个步骤之间的关系如下图所示:

而其中编译期的注解处理有两种机制,分别简单描述下:

Annotation Processing Tool(APT)

APT 自 JDK5 产生,JDK7 已标记为过期,不推荐使用,JDK8 中已彻底删除,自 JDK6 开始,可以使用 Pluggable Annotation Processing API 来替换它,APT 被替换主要有 2 点原因:

•相关 API 都在 com.sun.mirror 非标准包下•没有集成到 javac 中,需要额外运行

Pluggable Annotation Processing API(插件式注解处理器)

Java6 开始纳入了 JSR-269 规范:Pluggable Annotation Processing API(插件式注解处理器)。作为 APT 的替代方案,JSR-269 提供一套标准 API 来处理 Annotations,并解决了刚才提到的 APT 的两个问题。在使用 javac 的过程中,它产生作用的具体流程如下所示 👇

1.javac 对源代码进行分析,生成了一棵抽象语法树(AST)

2.运行过程中调用实现了「JSR 269 API」的 Lombok 程序

3.此时 Lombok 就对第一步骤得到的 AST 进行处理,找到@Data 注解所在类对应的语法树(AST),然后修改该语法树(AST),增加 getter 和 setter 方法定义的相应树节点

4.javac 使用修改后的抽象语法树(AST)生成字节码文件,即给 class 增加新的节点(代码块)

AST 是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构(Construct),例如包、类型、修饰符、运算符、接口、返回值甚至代码注释等都可以是一个语法结构。

想要实现一个基于「JSR 269 API」的程序也很容易具体来说,我们只需要继承AbstractProcessor类,重写process()方法实现自己的注解处理逻辑,并且在META-INF/services目录下创建javax.annotation.processing.Processor文件注册自己实现的 Annotation Processor,在 javac 编译过程中编译器便会调用我们实现的 Annotation Processor,从而使得我们有机会对 java 编译过程中生产的抽象语法树进行修改。

对于 Java 编译的一些补充

对于 Java 的编译期,其实是一个相对模糊的概念,需要针对具体的情况具体分析。

1.将*.java文件转为 *.class的过程称为编译器的前端(前端编译)。例如:JDK 的 javac 编译器。2.把字节码( *.class文件) 转变为 本地机器码 的过程称为 Java 虚拟机的即时编译运行期(JIT 编译器,Just In Time)。例如:HotSpot 虚拟机的 C1、C2 编译器。3.使用静态的提前编译器(AOT 编译器,Ahead Of Time Compiler)直接把程序变异成与目标及其指令集相关的二进制代码的过程。例如:JDK 的 Jaotc。

他们之间的关系大约是 👇

javac 把 *.java文件编译成*.class文件,*.class文件进入 JVM 后,通过 JIT 编译器将*.class文件解释为对应的机器码。而 AOT 则是直接把*.class文件编译系统的库文件,不再依靠 JIT 去做这个事情。

Javac 这类编译器对代码的运行效率几乎没有任何优化措施,但由于该阶段离程序员编码是最近的(相较于 JIT 而言),所以对于程序员编码来说,前端编译器在编译期的优化更加密切,许多新生的 Java 语法特性,都是靠编译器的「语法糖」(自动装箱、拆箱与遍历循环)来实现的,这是因为 Javac 做了很多针对 Java 语言编码过程的优化措施来改善程序员的编码风格、提升编码效率,而不是依赖虚拟机的底层改进来支持。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-02-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 01二进制 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 注解解析的两种方式
    • 运行时解析
      • 编译时解析
        • Annotation Processing Tool(APT)
          • Pluggable Annotation Processing API(插件式注解处理器)
          • 对于 Java 编译的一些补充
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档