几分钟了解下MapStruct的实现原理

一: 前言

最近项目上打算使用MapStruct框架来做JavaBean的映射,所以对MapStruct框架做了一个整体的探究,希望大家看完后能够对MapStruct框架有个整体上的了解。

二: 基本介绍

为了文章的严谨,我想借助于官网的介绍再合适不过了:

  • 是什么? MapStruct 是一个代码生成器,它基于约定而非配置方法,极大地简化了 Java Bean 类型之间映射的实现。 生成的映射代码使用普通方法调用,因此快速、类型安全且易于理解。
  • 为什么? 多层应用程序通常需要在不同的对象模型(例如实体和 DTO)之间进行映射。编写这样的映射代码是一项繁琐且容易出错的任务。MapStruct 旨在通过尽可能自动化来简化这项工作。 与其他映射框架相比,MapStruct 在编译时生成 Bean 映射,这确保了高性能,允许快速的开发人员反馈和彻底的错误检查。
  • 怎么做的? MapStruct 是一个注释处理器,它插入到 Java 编译器中,可以在命令行构建(Maven、Gradle 等)中使用,也可以在您喜欢的 IDE 中使用。

三: 技术点掌握

Java编译过程

我们都知道java编译将 ".java" 文件转换为 ".class" 字节码文件,最终 ".class" 字节码文件运行在jvm虚拟机之上,那java编译的过程是怎样的呢?下面简单用一张图来表示:

JSR269

用红色背景填充的"注解处理 "步骤就是我们要来说的JSR269规范:

  • 在JDK1.5之后,Java语言提供了对注解(Annotation)的支持,这些注解与普通的Java代码一样,是在运行期间发挥作用的。在JDK1.6中实现了JSR-269规范,JSR-269:Pluggable Annotations Processing API(插入式注解处理API)。提供了一组插入式注解处理器的标准API在编译期间对注解进行处理。我们可以把它看做是一组编译器的插件,在这些插件里面,可以读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环称为一个Round,这是一个回环过程。
  • 并且在此阶段,如果项目中有注解处理器想生成新的源文件。可使用JavacFiler创建新的源文件,这些源文件在此时被加入到编译过程中。它们被视为项目源代码的一部分,并将在接下来的步骤中一起被编译。

使用Freemarker模板文件快速生成java类文件

Freemarker是一款模板引擎:即一种基于模板和要改变的数据,用来输出文本(HTML网页、电子邮件),2002年编写者Attila在freemarker.ext*包里面支持了完成对javabean、jython、XML的映射。因此我们可以借助于这项模板引擎技术快速生成我们的java类。

实战

在这里我给大家找了两个可以快速入手上面技术点的实战例子,有兴趣的可以动起手来实操一个简单的demo体验下这些知识点:

JSR269实践

使用freemaker生成javabean

解析MapStruct实现

MapStcurt是在编译过程中生成类,我们这里是使用的是Maven包管理工具,Maven专门有命令(MvnDebug)提供给我们做编译时候的调试,然后我们可以通过idea配置远程调试看MapStruct的处理过程了。

准备调试环境

笔者这里演示使用的JDK是11版本,MapStruct是1.5.3.Final版本

运行MvnDebug命令

找到MapStruct注解处理类,在process方法上面打上断点

org.mapstruct.ap.MappingProcessor#process(java.util.Set<? extends javax.lang.model.element.TypeElement>, javax.annotation.processing.RoundEnvironment):

配置远程调试

探究MapStruct实现过程

点击调试按钮,即可进入断点:

1: 我们在源码META-INF/services目录下已经看到MapStruct配置的注解处理器了,所以它是基于JSR269进行实现的无可厚非。

2: 在断点org.mapstruct.ap.internal.writer.FreeMarkerModelElementWriter#write处,我们可以看到基于FreeMarker创建javabean:

2.1: 利用javacFiler创建sourceFile(后续会编译为.class字节码文件): org.mapstruct.ap.internal.processor.MapperRenderingProcessor#createSourceFile

2.2: 利用freemarker生成javabean:

org.mapstruct.ap.internal.writer.ModelWriter#writeModel

org.mapstruct.ap.internal.writer.FreeMarkerModelElementWriter#write

3: 如何获得这个生成类? 我们在使用MapStruct的使用方法org.mapstruct.factory.Mappers#getMapper(java.lang.Class)获取实现类,其内部其实是加载编译好的实现类,然后返回实现类:

org.mapstruct.factory.Mappers#doGetMapper

最后

笔者这里简单介绍了MapStruct的实现原理,大家可以想想这种方案做出来的javabean映射和我们经常使用的commons、spring里面的转换工具有啥不一样呢?又有哪些技术是基于JSR269做的呢?

相关推荐
沙蒿同学8 小时前
不引 Ghostscript:我用纯 Go 给 PDF 做了一场「压缩手术」
前端·后端·github
前进的李工9 小时前
飞书企业知识库设计+后端高频八股
数据库·redis·后端·mysql·性能优化·飞书·rag
金銀銅鐵11 小时前
[Java] 展示增删元素时 LinkedList 的内部变化
java·后端·ai编程
武子康12 小时前
Vercel Cache Reason:相同 MISS,怎样决定下一步查什么
人工智能·后端·agent
杨文说AI与数字化12 小时前
把模型依赖做成可替换的组件:三层适配 + 一套回归评估
后端
IT_陈寒13 小时前
Vue的这个响应式陷阱我居然现在才发现
前端·人工智能·后端
WL学习笔记13 小时前
使用string类
c++·后端·string类
飞鸟真人14 小时前
IM网关系列(九)单账号流量统计:滑动窗口桶方案设计与Go实现
后端·golang
武子康14 小时前
Embedding 替换怎么评测:把索引成本算进成功任务
人工智能·后端·agent
大哥430916 小时前
on-failure 不扛重启:一次 Docker restart 策略的反直觉排查
后端