mapstruct原理解析
目录
一、mapstruct简介
二、mapstruct与其他映射框架对比
三、mapstruct底层原理解析
1、Java动态编译
2、mapstruct包分析
3、使用方式
4、实现原理
四、小结
参考
一、mapstruct简介
mapstruct是一种实体类映射框架,能够通过Java注解将一个实体类的属性安全地赋值给另一个实体类。有了mapstruct,只需要定义一个映射器接口,声明需要映射的方法,在编译过程中,mapstruct会自动生成该接口的实现类,实现将源对象映射到目标对象的效果。
二、mapstruct与其他映射框架对比
实体类映射框架大致有两种:一种是运行期通过java反射机制动态映射;另一种是编译期动态生成getter/setter,在运行期直接调用框架编译好的class类实现实体映射。
由于mapstruct映射是在编译期间实现的,因此相比运行期的映射框架有以下几个优点:
- 安全性高。因为是编译期就实现源对象到目标对象的映射, 如果编译器能够通过,运行期就不会报错。
- 速度快。速度快指的是运行期间直接调用实现类的方法,不会在运行期间使用反射进行转化。
三、mapstruct底层原理解析
mapstruct是基于JSR 269实现的,JSR 269是JDK引进的一种规范。有了它,能够实现在编译期处理注解,并且读取、修改和添加抽象语法树中的内容。JSR 269使用Annotation Processor在编译期间处理注解,Annotation Processor相当于编译器的一种插件,因此又称为插入式注解处理。想要实现JSR 269,主要有以下几个步骤:
- 继承AbstractProcessor类,并且重写process方法,在process方法中实现自己的注解处理逻辑。
- 在META-INF/services目录下创建javax.annotation.processing.Processor文件注册自己实现的Annotation Processor
1、Java动态编译
Java程序编译一般经历以下流程:
图1 Java程序编译流程
上图中Java源码到class文件的过程其实是一个比较复杂的过程。其中的经过可以用下图描述:
图2 mapstruct编译过程
上图的流程可以概括为下面几个步骤:
- 生成抽象语法树。Java编译器对Java源码进行编译,生成抽象语法树(Abstract Syntax Tree,AST)。
- 调用实现了JSR 269 API的程序。只要程序实现了JSR 269 API,就会在编译期间调用实现的注解处理器。
- 修改抽象语法树。在实现JSR 269 API的程序中,可以修改抽象语法树,插入自己的实现逻辑。
- 生成字节码。修改完抽象语法树后,Java编译器会生成修改后的抽象语法树对应的字节码文件件。
2、mapstruct包分析
mapstruct主要有两个包:
- org.mapstruct:mapstruct:包含了映射相关的注解,如@Mapper、@Mapping等。
- org.mapstruct:mapstruct-processor:包含了注解处理器。用于处理注解相关的逻辑,如MappingProcessor等。
两个包大致包含的类如下图所示:
图3 org.mapstruct:mapstruct结构
图4 org.mapstruct:mapstruct-processor结构
3、使用方式
mapstruct的用法很简单,假设我们有两个实体类UserDto和UserVo,类定义如下:
public class UserDTO {private String name;private int age;private Date birthday;private int gender;private String idCard;
}public class UserVo {private String userName;private int age;private Date birthday;private int gender;private String idCard;
}
然后需要定义一个用例映射的接口,接口如下:
@Mapper
public interface UserConverter {@Mappings({@Mapping(source = "name", target = "userName")})UserVo userDtoToVo(UserDto userDto);}
在UserConverter接口上添加@Mapper注解,在编译的时候,mapstruct自动会生成一个UserConverter的实现类,实现userDtoToVo方法。userDtoToVo方法上的@Mappings主要用于特殊映射,比如上述的UserDto中的name想要映射成userName,则通过@Mappings告诉mapstruct将source中的name映射成target中的userName字段。
通过mapstruct处理后,自动生成UserConverterImpl类,类实现代码如下所示:
@Overridepublic UserVo userDtoToVo(UserDto userDto) {if ( userDto == null ) {return null;}UserVo userVo = new UserVo();if ( userDto.getName() != null ) {userVo.setUserName( userDto.getName() );}userVo.setAge( userDto.getAge() );if ( userDto.getBirthday() != null ) {userVo.setBirthday( userDto.getBirthday() );}if ( userDto.getGender() != null ) {userVo.setGender( Integer.parseInt( userDto.getGender() ) );}if ( userDto.getIdCard() != null ) {userVo.setIdCard( userDto.getIdCard() );}return userVo;}
4、实现原理
想要了解原理,可以从mapstruct的源码入手。上文介绍过mapstruct主要有两个jar包,通过根据JSR 269可以知道MappingProcessor就是mapstruct的入口。com.sun.tools.javac.main.JavaCompiler#compile方法的有段代码在编译的时候会调用到MappingProcessor。compile代码如图5所示:
图5 compile方法
MappingProcessor的process方法具体代码如下所示:
@SupportedAnnotationTypes({"org.mapstruct.Mapper"})
@SupportedOptions({"mapstruct.suppressGeneratorTimestamp", "mapstruct.suppressGeneratorVersionInfoComment", "mapstruct.unmappedTargetPolicy", "mapstruct.defaultComponentModel"})
public class MappingProcessor extends AbstractProcessor {public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {if (!roundEnvironment.processingOver()) {RoundContext roundContext = new RoundContext(this.annotationProcessorContext);Set<TypeElement> deferredMappers = this.getAndResetDeferredMappers();this.processMapperElements(deferredMappers, roundContext);Set<TypeElement> mappers = this.getMappers(annotations, roundEnvironment);this.processMapperElements(mappers, roundContext);}return false;}
想要用idea在编译的时候调试代码,可以根据下述步骤进行调试:
- 在终端里将路径切换到pom所在路径。例如pom的路径是/User/zhangsan/test/,则cd到该路径下。
- 在终端执行mvnDebug compile。执行该命令后,终端会提示已经监听了8000端口,如图6所示。(注意,执行mvnDebug之前得先配置maven的环境变量,否则无法识别这个命令)
- 在idea中创建添加Remote JVM Debug,端口号是8000,具体如图7所示。
- 在JavaCompiler里的compile方法打断点,再在AbstractProcessor#init和MappingProcessor#init打断点。然后在idea点击debug按钮进行debug。(注意如果本地编译的class已经是最近编译的,则点击debug后无法进入调试,需要修改下代码,如修改下UserDto里的属性,再点击debug按钮)
图6 终端执行mvnDebug命令
图7 idea 创建Remote JVM Debug
通过调试,我们可以看到执行的步骤是这样的:JavaCompiler#compile->AbstractProcessor#init->MappingProcessor#init->MappingProcessor#process。
MappingProcessor#process的代码如下所示:
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {if (!roundEnvironment.processingOver()) {RoundContext roundContext = new RoundContext(this.annotationProcessorContext);Set<TypeElement> deferredMappers = this.getAndResetDeferredMappers();this.processMapperElements(deferredMappers, roundContext);Set<TypeElement> mappers = this.getMappers(annotations, roundEnvironment);this.processMapperElements(mappers, roundContext);}return false;}
上述代码的this.processMapperElements(mappers, roundContext);就会执行具体的代码改造工作。具体代码就不做展示,可以自行跟进去查看。从processMapperElements可以看到会执行ModelElementProcessor接口的实现类里的process方法。分析下ModelElementProcessor可以发现它有多个实现类,具体实现类如图8所示。
图8 ModelElementProcessor实现类
ModelElementProcessor的实现类有多个,每个实现类有不同的作用,并且每个实现类都有一个priority,达到执行顺序的效果,实现类大致有如下几个:
MethodRetrievalProcessor:解析元素的方法等基本信息。priority=1。
MapperCreationProcessor:初始化MapperReference,解析出Mapper。priority=1000。
AnnotationBasedComponentModelProcessor:处理ComponentModel相关逻辑。priority=1100。AnnotationBasedComponentModelProcessor又有3个子类,主要用于实现JSR330、Spring component及Cdi 组件等功能。
MapperRenderingProcessor:创建接口的具体实现类,比如UserConverter接口,则生成UserConverterImpl类。priority=9999。
MapperServiceProcessor:处理spi和META-INF/services/下的相关逻辑。priority=10000。
从MapperRenderingProcessor类里可以看到有个createSourceFile方法,该方法会创建UserConverterImpl类,并写到特定目录下。这样就生成了UserConverter的实现类,里面有UserConverter里的所有方法。
四、小结
本文主要介绍mapstruct的实现原理。先简单介绍mapstruct是什么,然后对比mapstruct与其他框架的优势,最后分析mapstruct底层原理。分析mapstruct从JavaCompiler入手,一步一步往mapstruct里调试,能够清晰地了解底层的实现原理。想要了解某个框架的原理,最好还是从源码入手,自己亲自调试跟踪执行过程才能对原理了解得更透彻。
参考
https://blog.csdn.net/ni_hao_fan/article/details/99445073
https://www.freesion.com/article/9992598813/
mapstruct原理解析相关推荐
- Spark Shuffle原理解析
Spark Shuffle原理解析 一:到底什么是Shuffle? Shuffle中文翻译为"洗牌",需要Shuffle的关键性原因是某种具有共同特征的数据需要最终汇聚到一个计算节 ...
- 秋色园QBlog技术原理解析:性能优化篇:用户和文章计数器方案(十七)
2019独角兽企业重金招聘Python工程师标准>>> 上节概要: 上节 秋色园QBlog技术原理解析:性能优化篇:access的并发极限及分库分散并发方案(十六) 中, 介绍了 ...
- Tomcat 架构原理解析到架构设计借鉴
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 Tomcat 架构原理解析到架构设计借鉴 Tomcat 发展这 ...
- 秋色园QBlog技术原理解析:性能优化篇:数据库文章表分表及分库减压方案(十五)...
文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 3: 秋色 ...
- CSS实现元素居中原理解析
原文:CSS实现元素居中原理解析 在 CSS 中要设置元素水平垂直居中是一个非常常见的需求了.但就是这样一个从理论上来看似乎实现起来极其简单的,在实践中,它往往难住了很多人. 让元素水平居中相对比较简 ...
- 秋色园QBlog技术原理解析:Web之页面处理-内容填充(八)
文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 3: 秋色 ...
- 秋色园QBlog技术原理解析:UrlRewrite之无后缀URL原理(三)
文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 本节,将从 ...
- Android之Butterknife原理解析
转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! ##前言 Butterknife是一个专注于Android系统的View注入框架, ...
- 【深度学习】谷歌大脑EfficientNet的工作原理解析
[深度学习]谷歌大脑EfficientNet的工作原理解析 文章目录 1 知识点准备1.1 卷积后通道数目是怎么变多的1.2 EfficientNet 2 结构2.1 方式2.2 MBConv卷积块2 ...
最新文章
- java导入excle表格,并且对表格进行相应的修改,并对表格数据进行整理,最后导出本地表格等一系列...
- 【转】IIC总线的FPGA实现
- 今天浏览新闻的时候,发现一张图片特别有感触
- 设计模式学习笔记--Strategy 策略模式
- [蓝桥杯]最大连续子序列和
- springboot整合spring @Cache和Redis
- 请输入“您的生日”,格式:yyyy-MM-dd,使用程序计算您已经来到这个世界多少天了。
- mysql 触发器不能同时 insert or update or delete_MySQL6:触发器
- 哈佛大学教授:排名前5%学生的秘诀就3个字,这比勤奋更重要!
- 使用日志审计查看MaxCompute执行过哪些操作
- iOS内存管理系列之一:对象所有权与引用计数
- python图像识别坐标转换成g代码_python - 将坐标系导出为ESPG代码:to_epsg()或ExportToEPSG() - SO中文参考 - www.soinside.com...
- Spring Boot教程(二十五)关于RabbitMQ服务器整合
- 常用工具软件使用【2】
- 时序分析 30 金融资产预测 - 蒙特卡洛模拟
- 曲线平滑算法 matlab,matlab学习之降噪平滑算法
- Tianchi big data competition天池大数据挑战(CV场)
- 陈佩斯曾受邀喜剧综艺:被酬劳吓的恍惚好几天
- 使用libimobiledevice在linux上挂载iphone6
- chall.tasteless.eu 中的注入题
热门文章
- Nginx以及通过Nginx实现tomcat集群配置与负载均衡
- 无法启动fireworks,内部发生错误
- 弹弹堂为什么我早上登陆显示加载服务器列表失败fail,弹弹堂加载服务器列表失败...
- python function at 0x00000_Python 中的函数装饰器和闭包
- Linux 搜索文件夹内的文件关键字
- 新媒体运营胡耀文教程:如何提高产品转化率?
- tinyurl_如何查看TinyUrl真正链接到的位置
- Chrome扩展程序添加CRX插件显示“程序包无效”
- oracle undefine的用法_oracle define,undefine,ACCEPT,以及变量的获取 命令
- Linux/Centos服务器带宽异常跑满的排查解决办法