掘金 后端 ( ) • 2024-04-12 16:34

theme: smartblue

一: 前言

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

二: 基本介绍

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

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

三: 技术点掌握

Java编译过程

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

image.png

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命令

image.png

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

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

image.png

配置远程调试

image.png

探究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 image.png

2.2: 利用freemarker生成javabean:

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

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

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

org.mapstruct.factory.Mappers#doGetMapper

image.png

最后

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