Lombok原理探析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: ## 前言对于一个Java开发者来说,Lombok应该是使用最多的插件之一了,他提供了一系列注解来帮助我们减轻对重复代码的编写,例如实体类中大量的setter,getter方法,各种IO流等资源的关闭、try…catch…finally模版等,虽然可以通过IDE的快捷帮我们生成这些方法,但这些冗长的代码仍会影响代码的简洁性与可阅读性。如今,随着使用者数量越来越多,Lombok甚至成为IDEA的

前言

对于一个Java开发者来说,Lombok应该是使用最多的插件之一了,他提供了一系列注解来帮助我们减轻对重复代码的编写,例如实体类中大量的setter,getter方法,各种IO流等资源的关闭、try…catch…finally模版等,虽然可以通过IDE的快捷帮我们生成这些方法,但这些冗长的代码仍会影响代码的简洁性与可阅读性。
如今,随着使用者数量越来越多,Lombok甚至成为IDEA的内置插件了(2020.3 版本+),可见其影响力。
20201209172523435.jpg
但不知道使用Lombok的你,是否思考过这种自动生成代码究竟是什么原理呢?这些代码又是怎么产生的呢?而这就是本文要展开介绍的内容了。

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

注解解析的两种方式

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

运行时解析

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

@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方法呢?这就不得不说另一种注解解析方式了——编译时解析

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
    String staticConstructor() default "";
}

编译时解析

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

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

这 3 个步骤之间的关系如下图所示:
image.png
而其中编译期的注解处理有两种机制,分别简单描述下:

Annotation Processing Tool(APT)

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

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

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

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

  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。

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

Javac 这类编译器对代码的 运行效率几乎没有任何优化措施,但由于该阶段离程序员编码是最近的(相较于JIT而言),所以对于程序员编码来说,前端编译器在编译期的优化更加密切,许多新生的 Java 语法特性,都是靠编译器的 「语法糖」(自动装箱、拆箱与遍历循环)来实现的,这是因为 Javac 做了很多针对 Java 语言编码过程的优化措施来改善程序员的编码风格、提升编码效率,而不是依赖虚拟机的底层改进来支持。
相关文章
|
22天前
|
安全 Java API
JAVA并发编程JUC包之CAS原理
在JDK 1.5之后,Java API引入了`java.util.concurrent`包(简称JUC包),提供了多种并发工具类,如原子类`AtomicXX`、线程池`Executors`、信号量`Semaphore`、阻塞队列等。这些工具类简化了并发编程的复杂度。原子类`Atomic`尤其重要,它提供了线程安全的变量更新方法,支持整型、长整型、布尔型、数组及对象属性的原子修改。结合`volatile`关键字,可以实现多线程环境下共享变量的安全修改。
|
2月前
|
监控 算法 Java
Java内存管理:垃圾收集器的工作原理与调优实践
在Java的世界里,内存管理是一块神秘的领域。它像是一位默默无闻的守护者,确保程序顺畅运行而不被无用对象所困扰。本文将带你一探究竟,了解垃圾收集器如何在后台无声地工作,以及如何通过调优来提升系统性能。让我们一起走进Java内存管理的迷宫,寻找提高应用性能的秘诀。
|
18天前
|
算法 Java
JAVA并发编程系列(8)CountDownLatch核心原理
面试中的编程题目“模拟拼团”,我们通过使用CountDownLatch来实现多线程条件下的拼团逻辑。此外,深入解析了CountDownLatch的核心原理及其内部实现机制,特别是`await()`方法的具体工作流程。通过详细分析源码与内部结构,帮助读者更好地理解并发编程的关键概念。
|
13天前
|
Java
lombok的使用
本文介绍了Lombok库的基本使用方法和常用注解,通过示例代码展示了如何使用Lombok简化Java对象的创建、属性访问、日志记录等编码工作,使代码更加简洁。
lombok的使用
|
2天前
|
网络协议 安全 Java
Java Socket原理
Java Socket原理是指在Java中通过Socket实现的网络通信的基础理论与机制。Socket是网络中不同设备间通信的一种标准方式,它允许应用程序之间通过TCP/IP等协议进行数据交换。在Java中,利用Socket编程可以方便地创建客户端与服务器端应用,实现跨网络的数据传输功能,是互联网软件开发中的重要技术之一。它支持多种通信模式,如可靠的流式套接字(TCP)和数据报式套接字(UDP)。
|
17天前
|
Java
JAVA并发编程系列(9)CyclicBarrier循环屏障原理分析
本文介绍了拼多多面试中的模拟拼团问题,通过使用 `CyclicBarrier` 实现了多人拼团成功后提交订单并支付的功能。与之前的 `CountDownLatch` 方法不同,`CyclicBarrier` 能够确保所有线程到达屏障点后继续执行,并且屏障可重复使用。文章详细解析了 `CyclicBarrier` 的核心原理及使用方法,并通过代码示例展示了其工作流程。最后,文章还提供了 `CyclicBarrier` 的源码分析,帮助读者深入理解其实现机制。
|
10天前
|
安全 Java 编译器
Java反射的原理
Java 反射是一种强大的特性,允许程序在运行时动态加载、查询和操作类及其成员。通过 `java.lang.reflect` 包中的类,可以获取类的信息并调用其方法。反射基于类加载器和 `Class` 对象,可通过类名、`getClass()` 或 `loadClass()` 获取 `Class` 对象。反射可用来获取构造函数、方法和字段,并动态创建实例、调用方法和访问字段。虽然提供灵活性,但反射会增加性能开销,应谨慎使用。常见应用场景包括框架开发、动态代理、注解处理和测试框架。
|
18天前
|
Java
Java的aop是如何实现的?原理是什么?
Java的aop是如何实现的?原理是什么?
18 4
|
22天前
|
存储 Java
JAVA并发编程AQS原理剖析
很多小朋友面试时候,面试官考察并发编程部分,都会被问:说一下AQS原理。面对并发编程基础和面试经验,专栏采用通俗简洁无废话无八股文方式,已陆续梳理分享了《一文看懂全部锁机制》、《JUC包之CAS原理》、《volatile核心原理》、《synchronized全能王的原理》,希望可以帮到大家巩固相关核心技术原理。今天我们聊聊AQS....
|
19天前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。