首页 > 其他分享 >浅析Lombok与MapStruct的实现原理

浅析Lombok与MapStruct的实现原理

时间:2024-09-28 17:47:27浏览次数:1  
标签:反射 lombok Java MapStruct 编译 处理器 注解 Lombok 浅析

本篇主要从Java代码的编译视角简要去对Lombok、MapStruct的实现原理进行说明,如有谬误,恳请斧正。

可能会涉及到分析的内容:

  1. 编译原理
  2. 反射机制
  3. APT注解处理器
  4. JSR269
  5. SPI服务发现机制

一、背景概述

最近,参与组内的MapStruct的替换,主要是用于优化对象拷贝、类转换这两种场景,这件事其实在去年实习时就已经在业务域推行,因此G老师让我来主导这件事。

事实上,实习与转正后不同,由于组织架构调整,我已经不在原来的业务域,或者说比较幸运地得到G老师的赏识,在他被调到另外一个业务域时,主动向上面请求带上我,所以现在来到了一个新的业务域。

而原先的业务域早已完成替换,时隔半年,这次要在另外一个业务域推行,但实习时并没有参与到这件事中,所以这次也算是对我的一次锻炼。

其实早在第一次准备八股时,我就对一件事很好奇,Java代码是如何运行的呢?在C++中我大体知道编译、链接以及静态库和动态库的一些概念,在Linux上也用命令行去操作过makefile文件。

但也许是使用Java的IDE成为常态了,编译后的target目录我也极少翻看,即使我使用txt文件编写过Java代码,也仅仅了解到javac编译器与生成class文件这两个现象而已,因此我在去年五六月份时,做过简单了解,恰巧与这次MapStruct使用的知识相关,所以尝试将两者串联起来,也顺便作为到时候梳理文档的一个逻辑线。

二、编译流程

其实想要分析Lombok与MapStruct便绕不开一个东西,那便是注解处理器APT,这个东西便是在Java代码编译过程中使用的,所以我便干脆把整个编译流程简单梳理一遍。

早在lombok出现时,我想应该就会有人好奇,以下问题:

为什么Lombok可以直接在编译后生成,而不影响性能?

早期lombok为什么有可能会引发各种各样的问题,导致至今都有公司弃之如敝履?

而Java中的注解又怎么会有编译处理与运行处理两大类,编译处理是如何实现的?

javac作为编译器,到底做了什么,在其中扮演着什么样的角色?

究其根本,还是要先回到代码编译的过程中。

Java文件到Class文件中间发生了什么?如果作为一个计算机专业方向的学生,应该都还记得一门课,编译原理。无论是C++、Java,亦或是其他语言,甚至是Mysql和现在大火的LLM,只要与语言相关,从一个语言体系到另一个语言体系都绕不开这件事“编译”,而这个过程,便是构建语法树的过程。

如果还记得编译原理的话,那么应该能回忆起两个阶段,词法分析和语法分析,这大概便是编译这件事的通用流程了,那么在此列出Java编译的四个模块。

  1. 词法分析器:识别关键词,构造标准token流
  2. 语法分析器:根据token流构建抽象语法树
  3. 语义分析器:简化语法树
  4. 字节码生成器:将语法树翻译成字节码

这四个模块也被我理解为四个步骤,也即识别、构建、简化和翻译,但这不是编译过程中的全部,至少不是Java编译的全部,因此有些人会认为Java编译有七大阶段甚至更多。

但我认为从编译原理的角度出发,其他的内容不过是从一个阶段到另一个阶段之间还隐藏着些许细节,在此不做过多扩展,只挑出与本篇相关的内容来展开。

在Java进行语义分析时,做的是简化操作,主要是对语法糖进行处理,那么抽象语法树怎么会那么大跨度来到可以进行语义分析呢?其实在语法分析到语义分析过程中,javac还进行了两个操作,一是对符号表填充,二是对注解进行处理,在此,我们展开后者进行简单说明。

前文提到过,注解处理分为编译期处理和运行期处理,而运行期处理是通过反射实现的,编译期呢,这就涉及到前文提到的另一个东西——注解处理器(APT)。

注解处理器也许对于很多人来说并不熟悉,但它很好理解,所谓的处理器,其实不过是与替换类似,只不过这个替换的步骤是由代码逻辑实现的,最终的现象就类似于有了这个注解,就会被处理器替换成一段逻辑相同的代码。APT主要进行的操作如下:

  1. 扫描并解析注解
  2. 对相应的注解进行相应的逻辑实现
  3. 生成代码
  4. 将生成的代码进行编译

在整个编译过程中,需要处理的地方被重新编译后,相当于重新生成的子树一样,直接拼接到原先的抽象语法树的位置即可。

三、实现原理

了解完注解处理器是否联想到了什么?对,如果我定义了一个注解处理器,是对当前这个类识别后,生成属性的get和set方法是不是说明我实现了lombok?如果我在这里实现各种类的转换和拷贝是不是说明我实现了mapstruct?

这就是lombok与mapstruct的原理,就是这么顺其自然,那么如何实现呢?这就不得不引入另外一个东西了JSR269-插入式注解处理器,JSR是Java的一种规范与约定,269代表具体的条数,常见的还有JSR330是有关注解的使用的。

JSR269提出了一种约定,即Java通过约定的方式去识别注解处理器,在编译时对注解进行处理,如果这些处理如果这些处理器在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环称为一个Round

而这种约定,也相当于说明,可以由使用者按照约定去实现注解处理器了,具体的实现方式类似在约定的路径上,继承约定类,则会在编译时执行,具体的实现流程比较繁琐,同时也涉及语法树的解析处理,lombok所需的插件便是类似作用,其中详细知识笔者也是一知半解,便不在此过度展开。那么这个步骤是不是又与某个内容关联起来了?是的,这其中也有赖于Java的SPI服务发现机制,对于约定下新增的具体实现也会被纳入执行范围内。

四、使用优势

回到本次的lombok和mapstruct上,在使用mapstruct后,类转换和拷贝可以说完全在编译期实现,对服务运行不会再造成性能上的影响,组内原先使用的反射的方式被替换后,各项指标立马陡降50%,其中CPU使用率从30%+到15%左右,这还是没有全部替换的情况下。

不过,将事情放到编译期去做,在执行不规范的情况下,很有可能会打乱原有的编译流程,这也是lombok和mapstruct使用时容易出现问题的原因,尤其是同时使用时,如果mapstruct的处理流程在lombok之前,就会导致set与get方法缺失,从而引发一系列问题,或是注解处理器指定后使原先Java自带的注解处理器被覆盖无法运行,如此种种,不一而足,如今也有足够多的博客去列出各种情况的解决方案,笔者便不再赘述。

五、关于反射

其实,MapStruct带来的优势,很大一部分程度是使用反射进行对象拷贝和类转换的劣势。反射这种方式由于使用简便,功能强大,但又存在性能损耗,很容易被滥用,大家过度的关注磁盘IO、网络IO这些瓶颈,却忽视了“小小的损耗”,积沙成塔,这与IPC和RPC分离的历史相似。

RPC作为服务间远程调用的方式,被是作为IPC的一种特例,早先在被设计时,是希望同IPC一般,直接被使用,将具体实现流程隐藏在操作系统层面,而使用者无需关注其中的细节,但很快设计者们就发现由于RPC内部细节被屏蔽后,导致RPC的滥用,使用者错误地认为RPC是毫无时间损耗的。

而反射的性能损耗从何而来?如果有关注反射的人也许会知道,反射是通过Method从Class类向JVM获取内容的,实际上是通过类加载器重新获取了一遍类,在类的元数据中寻找,涉及到更多的指针和内存操作,增加了开销,至于安全性检查、拆装箱等内容,在此也不再过多赘述。

实际上,可以通过关闭安全检查、缓存反射构造器来降低反射的影响,当然具体情况需要具体讨论,笔者也仅仅是在试图了解更多反射相关的知识时接触到这些内容的,并未在具体场景中实践过。

标签:反射,lombok,Java,MapStruct,编译,处理器,注解,Lombok,浅析
From: https://www.cnblogs.com/youngfun/p/18438195

相关文章

  • 建造者模式与lombok
    当一个类的参数过多,并且大部分时候这些参数都是可选参数的时候,我们就会考虑使用建造者模式lombok插件中提供了Builder方法@Data@BuilderpublicclassCase{privateStringname;privateStringdesc;}编译后的case类publicclassCase{privateString......
  • lombok~避免Boolean属性使用默认的方法
    前言前端与后端在进行交互时,前端传的对象中,有的属性是int,boolean这些类型,它们是有默认值的,当前端不转它们时,后端也会接收到默认值,如int为0,boolean为false,这就给后端代码带来不确定性;比如要有个put更新用户的方法,有个用户对象如下@DatapublicclassUserAccountResponseimplem......
  • 关于 ReentrantLock 中锁 lock() 和解锁 unlock() 的底层原理浅析
    关于ReentrantLock中锁lock()和解锁unlock()的底层原理浅析一、描述如下代码,当我们在使用ReentrantLock进行加锁和解锁时,底层到底是如何帮助我们进行控制的啦?staticLocklock=newReentrantLock();publicstaticvoidmain(String[]args){/......
  • 浅析电动汽车虚拟电厂的优化调度
    摘要:大量电动汽车用户的无序充电可能造成电网负荷剧烈波动,危及电网的安全稳定。随着电动汽车入网技术的应用,将电动汽车充电站及其周边的分布式新能源发电聚合为虚拟电厂后进行优化调度,有助于改善电动汽车用户充放电的经济性及满意度,同时提高分布式新能源的利用率,平抑电网负荷波动,但......
  • MapStruct 超神进阶用法,让你的代码效率提升十倍!
     MapStruct是一个强大且灵活的映射框架,很好的解决有关对象转换的问题,实现了代码的简洁和性能的兼顾。MapStruct的常规用法,网上有很多教程了,本文将列举一些进阶用法,方便日常开发使用。expression在转化的时候,执行java表达式,直接看例子:@Mapper(componentModel="spring")public......
  • MapStruct 超神进阶用法,让你的代码效率提升十倍!
     MapStruct是一个强大且灵活的映射框架,很好的解决有关对象转换的问题,实现了代码的简洁和性能的兼顾。MapStruct的常规用法,网上有很多教程了,本文将列举一些进阶用法,方便日常开发使用。expression在转化的时候,执行java表达式,直接看例子:@Mapper(componentModel="spring")public......
  • 浅析OceanBase数据库的向量化执行引擎
    本篇博客是偏数据库系统概念性的内容,不会深入到OceanBase中各个算子和表达式的在向量化中的详细设计和实现。背景为了提升OceanBase社区版用户解决问题的效率,OceanBase官方不久前推出了《OceanBase从入门到实践》系列课程。在第七期直播课程后,我们收到了不少用户的提......
  • lombok编译遇到“找不到符号的问题”
    问题描述最近编译使用了lombok依赖的maven项目遇到了一个没有遇到过的现象。项目代码可以正常运行,但是只要一使用maven:clean再打包就会报lombok注解的类“找不到符号”。按照几年的开发经验真的头疼了一段时间,查了很多帖子没有人遇到过,感觉十分匪夷所思。之后分析了整个maven:c......
  • 第十一章 【后端】商品分类管理微服务(11.2)——Lombok
    11.2Lombok官网:https://projectlombok.org/较新版本的idea已默认安装lombok插件Lombok工具提供一系列的注解,使用这些注解可以不用定义getter、setter、equals、constructor等,可以消除java代码的臃肿,编译时它会在字节码文件中自动生成这些通用的方法,简化开发人......
  • Python中 递归(Recursion)的使用浅析
    递归的定义递归是一种在函数定义中调用函数自身的编程技巧和算法设计方法。递归中有两个关键要素1. 递归的终止条件。当满足这个条件时,递归不再继续调用自身,而是开始返回结果。这也叫 递归基例(BaseCase)。 如果没有正确设置递归基例,递归函数将无限地调用自身,直到耗尽系......