MapStruct架构设计(mapstruct官方文档)
zhezhongyun 2025-07-23 19:24 3 浏览
MapStruct架构原理及改造
一、前言 4
二、什么是语法树(AST) 4
2.1 Java编译时的三个阶段 4
三、什么是JSR269 5
3.1 使用步骤 5
3.2 流程图 6
四、源码架构分析 6
4.1 MappingProcessor 7
4.2 MethodRetrievalProcessor 10
4.3 MapperCreationProcessor 11
4.3.1 ValueProvider 13
4.3.2 MappingResolverImpl 13
4.4 MappingRenderingProcessor 14
一、前言
为什么用MapStruct?
MapStruct 是一个生成类型安全, 高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)。
抓一下重点:
- 注解处理器
- 可以生成 JavaBean 之间那的映射代码
- 类型安全、高性能、无依赖性
从字面的理解,我们可以知道,该工具可以帮我们实现JavaBean之间的转换,通过注解的方式。同时,作为一个工具类,相比于手写, 其应该具有便捷, 不容易出错的特点。
MapStruct是基于JSR 269的Java注解处理器,因此可以在命令行构建中使用(javac、Ant、Maven等等),可以在IDE内使用。用于生成类型安全的bean映射类的Java注解处理器。属于编译时注解,如果转换bean内容有变化。需要手动clean下才能将变化的内容体现到class文件中。说白了就是通过注解的形式帮我们生成set,get方法。
MapStruct的核心是在编译期生成基于转换规则的Impl文件,运行时直接调用Impl文件中的函数,整个MapStruct的过程分为三个部分:
- 自定义注解,指定转换规则,例如:source,target等。
- freemarker模板,用来生成Impl文件。
- 基于 javax.annotation.processing 的处理模块
二、什么是语法树(AST)
AST是javac编译器阶段对源代码进行词法语法分析之后,语义分析之前进行的操作。
用一个树形的结构表示源代码,源代码的每个元素映射到树上的节点。
2.1 Java编译时的三个阶段
Java源文件---->词法,语法分析----> 生成AST ---->语义分析 ----> 编译字节码,二进制文件。
通过操作 AST 可以实现 java 源代码的功能。
Rewrite、JavaParser 等开源工具可以帮助你更简单的操作AST。
1、所有源文件会被解析成语法树。
2、调用注解处理器。如果注解处理器产生了新的源文件,新文件也要进行编译。
3、最后,语法树会被分析并转化成类文件。
三、什么是JSR269
插件化注解处理(Pluggable Annotation Processing)APIJSR 269提供一套标准API来处理AnnotationsJSR 175,实际上JSR 269不仅仅用来处理Annotation,我觉得更强大的功能是它建立了Java 语言本身的一个模型,它把method、package、constructor、type、variable、enum、annotation等Java语言元素映射为Types和Elements,从而将Java语言的语义映射成为对象,我们可以在javax.lang.model包下面可以看到这些类。所以我们可以利用JSR 269提供的API来构建一个功能丰富的元编程(metaprogramming)环境。
JSR 269用Annotation Processor在编译期间而不是运行期间处理Annotation, Annotation Processor相当于编译器的一个插件,所以称为插入式注解处理。如果Annotation Processor处理Annotation时(执行process方法)产生了新的Java代码,编译器会再调用一次Annotation Processor,如果第二次处理还有新代码产生,就会接着调用Annotation Processor,直到没有新代码产生为止。每执行一次process()方法被称为一个"round",这样整个Annotation processing过程可以看作是一个round的序列。
JSR 269主要被设计成为针对Tools或者容器的API。这个特性虽然在JavaSE 6已经存在,但是很少人知道它的存在。lombok就是使用这个特性实现编译期的代码插入的。另外,如果没有猜错,像IDEA在编写代码时候的标记语法错误的红色下划线也是通过这个特性实现的。KAPT(Annotation Processing for Kotlin),也就是Kotlin的编译也是通过此特性的。
Pluggable Annotation Processing API的核心是Annotation Processor即注解处理器,一般需要继承抽象类
javax.annotation.processing.AbstractProcessor。注意,与运行时注解RetentionPolicy.RUNTIME不同,注解处理器只会处理编译期注解,也就是RetentionPolicy.SOURCE的注解类型,处理的阶段位于Java代码编译期间。
3.1 使用步骤
- 自定义一个Annotation Processor,需要继承java.annotation.processing.AbstractProcessor并覆写process方法。
- 自定义一个注解,注解的元注解需要指定@Retention(RetentionPolicy.SOURCE)。
需要在声明的自定义Annotation Processor中使用如下注解
javax.annotation.processing.SupportedAnnotationTypes指定在第2步创建的注解类型的名称(注意需要全类名,“包名.注解类型名称”,否则会不生效)。
- 需要在声明的自定义Annotation Processor中使用javax.annotation.processing.SupportedSourceVersion指定编译版本。
- 可选操作,可以通过声明的自定义Annotation Processor中使用javax.annotation.processing.SupportedOptions指定编译参数。
3.2 流程图
四、源码架构分析
打开MapStruct源码后,看到如下包结构图:
本文涉及修改MapStruct源码,所以只分析processor包结构,这是MapStruct的核心代码,用来通过freeMarker生成代码的引擎。
4.1 MappingProcessor
MappingProcessor遵循了JSR269规范,负责生成使用了@Mapper注解的映射器接口实现,然后将其写入到Java源文件中。而模型实例化和处理是通过一系列的Processor责任链来实现,这些Processor是使用Java的ClassLoader机制进行加载的。
下图是用serviceClassLoader去加载所有定义好的Process类,形成类似于处理链,类似于责任链的一种方式(用数组记录执行节点而不是用链表)
加载的Processor类是从META-INF中加载文件是
org.mapstruct.ap.internal.processor.ModelElementProcessor,内容如下图所示:
各Processor之间的调用如下图所示:
再回到MappingProcessor类中,整个流程的入口方法是process(),从process方法中先来看如下图所示代码:
这个类对象的接口是MappingResovler,主要是解析我们方法中的元素(比如property,iterable等),从源映射到目标,有两个基本的操作,一个是转换,一个是方法。转换就是将方法中的参数,比如String映射到Integer,或是将Integer转换到Long这种。我们在构建MappingResolverImpl类的时候,通过typeFactory构建了Conversion类,如下图所示:
在Conversions注册了所有的类型映射转换,如下图所示:
我们的新类型将List转换为String的操作就是在这个类中进行,映射类是ListToStringConversion。之后返回到MappingProcessor类中进入到processMapperElements方法如下所图示:
在这个process方法中启动前面提到过的责任链,如下图所示:
这七个执行器形成一个调用链,后面的核心流程主要也是围绕这七个运行。这七个运行器的作用如下:
- MethodRetrievalProcessor:解析元素的方法等基本信息。priority=1。
- MapperCreationProcessor:初始化MapperReference,解析出Mapper。priority=1000。
- AnnotationBasedComponentModelProcessor:处理ComponentModel相关逻辑。priority=1100。AnnotationBasedComponentModelProcessor又有3个子类,主要用于实现JSR330、Spring component及Cdi 组件等功能,这个类是CdiComponentProcessor和SpringComponentProcessor以及JSr330ComponentProcessor的父类。
- MapperRenderingProcessor:创建接口的具体实现类,比如UserConverter接口,则生成UserConverterImpl类。priority=9999。从MapperRenderingProcessor类里可以看到有个createSourceFile方法,该方法会创建UserConverterImpl类,并写到特定目录下。这样就生成了UserConverter的实现类,里面有UserConverter里的所有方法。
- MapperServiceProcessor:处理spi和META-INF/services/下的相关逻辑。priority=10000。
4.2 MethodRetrievalProcessor
这个Processor的核心方法是:
private List<SourceMethod> retrieveMethods(TypeElement usedMapper, TypeElement mapperToImplement, MapperOptions mapperOptions, List<SourceMethod> prototypeMethods)
这个方法的作用是通过给定的Mapper类型,检索需要映射的方法。
这个方法主要就是解析映射接口方法:
由图可知,我要转换的方法是fromMap,参数是一个Map参数。
4.3 MapperCreationProcessor
这个类的入口方法是process,核心方法是private Mapper getMapper(TypeElement element, MapperOptions mapperOptions, List<SourceMethod> methods)。
方法流程如下:
- 通过内部的getMappingMethod方法,判断映射接口方法是不是枚举方法,是不是继承方法,是不是流方法等。
- 通过BeanMappingMethod.build构建源方法和参数与目标方法和参数的映射,如果映射接口的方法返回不是是isVolid,则获取返回类型,比如返回目标对象是Order类型,通过Type.resultTypeToMap.getPropertyWriteAccessors( cms )获取目标对象的所有可访问的方法,返回的内容如下所示,是一个Map结构:
- 通过BeanMappingMethod.build方法构建目标属性targetProperties和未处理的目标属性unprocessedTargetProperties,以及unprocessedSourceParamters未处理的源参数。
- 判断源方法中的参数是否是Map类型(这是我新加的判断)
- BeanMappingMethod.build().handleDefinedMappings(),这个方法将迭代所有的映射方法,如果这些源和目标的方法之前就已经匹配过了,就从属性对象中删除。
- BeanMappingMethod.build().applyPropertyNameBasedMapping(),迭代所有目标属性和源参数。方法调用getSourceRefByTargetName(Parameter sourceParameter, String targetPropertyName)这个方法,从目标字段名来匹配源,这个方法很重要继续深挖,判断源参数类型是不是Map,我们的例子中需要转换的对象就是Map(注意这也是新加的方法),如果是Map则获取所以参数。
可以看到typeParameters的类型都是字符串,如下图所示:
如果typeParameters.size等于2,也就是Map中有key和value两个属性,则执行
SourceReference.fromMapSource方法。访问返回SourceReference对象,因为是源对象是Map对象,所以SourceReference对象的内容如下图所示:
从图中可以看到,Map对象的key是list,类型是字符串,如果有多个属性则以此类推。
- 在BeanMappingMethod.build()中调用
applyPropertyNameBasedMapping(List<SourceReference> sourceReferences)方法,在这个方法中构建PropertyMapping对象,这个对象是构建源和目标属性之间的映射,源和目标属性之间的字段名字可能是不同的,如果不同,则通过调用标识注解来做对应关系。
同时通过(String sourceRef = sourceParam.getName() + "." + ValueProvider.of(
propertyEntry.getReadAccessor() );代码来拼接属性方法的访问,关于ValueProvider对象的用法参考2.3.1节。返回值参数下图:
4.3.1 ValueProvider
ValueProvider是包装类,提供了模型中需要用到的get,set方法,这是一个模板,最终用来生成代码中用到的。
代码如图所示:
红框部分是我新增的部分,用来判断参数类型是不是Map,如果是的话模板就用get(“xxx”)。如果是普通属性则用getXX()这种方式返回,最终以ValueProvider对象的方式返回。
4.3.2 MappingResolverImpl
通过PropertyMapping中的getTargetAssignment方法找到MappingResolverImpl对象
这个类最重要的代码如下图所示:
通过resolveViaConversion方法可以找到之前注册进来的ListToStringConversion转换器。
注意:红框中的代码是我新加的,可以不断扩展,这段代码判断当前属性类型为List的时候,对应执行ListToStringConversion转换器。将对应的属性与属性类型进行结合,参考map.get(“xxx”),可以参考
PrimitiveToStringConversion的例子:
转换完的表达式模板如下图所示:
- XX
执行完2.3.2小节的内容后,方法返回到BeanMappingMethod->MapperCreationProcessor中.
4.4 MappingRenderingProcessor
这个Processor主要是创建内容并且将内容写入到文件中,从process入口方法中跟踪Mapper对象内容,如下图所示:
可以看到在Mapper中已经有packageName和name,而name已经在映射接口名后面自动加了Impl后缀。最终类文件和内容通过ModelWriter调用FreeMarker生成写入。
相关推荐
- C#.NET NLog 详解(c#nuget)
-
简介NLog是.NET平台上最流行的开源日志框架之一,特色是灵活的配置、丰富的输出目标(Target),以及高性能的异步写入能力。适用场景:从控制台、文件、数据库、网络到Elastic...
- WPF中datagrid单元格背景颜色(wpf datagrid单元格编辑)
-
datagrid中AutoGenerateColumns="true",使用viewmodel中绑定的数据源。后台代码找到目标字段,重写IValueConverter值转换器接口,根...
- Unity Profiler实战指南:从卡顿到丝滑的性能优化之旅
-
当玩家说"这游戏卡爆了"时,你该怎么办?"角色移动时帧率从60掉到20,技能特效一放直接卡成PPT"——这是《幻境冒险》项目上线前测试阶段收到的玩家反馈。作为主程的我知...
- Windows下取文件属性特例(windows文件属性快捷键)
-
今日碰到有程序在我们产品系统环境下无法正常运行某些功能,使用ProcessMonitor加反复测试发现,与产品中创建的symlink(软链接)有关。具体来讲,symlink文件是一个软链接文件,它的...
- 展开说说,DOS有哪些常用、实用的命令?
-
晚上好,我是老杨,今天来聊聊常用的DOS命令。虽然是老古董,但不妨碍它的好用程度。可能一些新手不晓得,但是和老杨一个年纪的,一定对DOS系统不陌生。尽管现在大多数电脑的操作系统是Windows,但在W...
- webservice更改返回信息节点名称(webservice返回值)
-
问题详情:<!--访问webservice中,返回的信息--><soap:Envelopexmlns:soap="http://schemas.xmlsoap.org/s...
- Windows Server 2019 基线检查表 (1)
-
ControlSetCorrectlyYesNo1AccountPolicies1.1PasswordPolicy1.1.1(L1)Ensure'Enforcepasswordh...
- MapStruct架构设计(mapstruct官方文档)
-
MapStruct架构原理及改造一、前言4二、什么是语法树(AST)42.1Java编译时的三个阶段4三、什么是JSR26953.1使用步骤53.2流程图6四、源码架构分析64.1...
- Excel常用技能分享与探讨(6-实战小功能分享 三)
-
书接上文,上一篇主要写了如何用代码动态创建控件,这一章讲讲如何具体实现我们需要的功能。五、功能性代码我们添加两个OptionButton的目的就是为了切换到对应的工具,所以,我们需要的是在点击了相对应...
- 30天学会Python编程:8. Python面向对象编程
-
8.1OOP基础概念8.1.1面向对象三大特性8.1.2类与对象关系核心概念:类(Class):对象的蓝图/模板对象(Object):类的具体实例属性(Attribute):对象的状态/数据方法...
- 环境变量设置被禁止临时方案(为什么设置了环境变量还是会出现)
-
1,到官网下载ant安装包。官网下载地址:ApacheAnt-BinaryDistributions2,解压到本地不带中文目录下,我这儿是D盘并且改名为ant3,设置环镜变量时,发现云桌面系统...
- 一文读懂 JavaScript依赖注入(java依赖注入简单理解)
-
大家好,我是Echa。依赖注入DI(DependencyInjection)是编程领域中一个非常常见的设计模式,它指的是将应用程序所需的依赖关系(如服务或其他组件)通过构造函数参数或属性自动...
- TypeScript 熟练度自测:6 道题检验你的 TS 功底!
-
这些题目既可以测试基本的类型知识,也能考察面向对象编程、泛型、类型推导和高级类型等方面的能力。以下是几个我会出题的方向和具体题目:1.类型推导与基础类型目的:考察应聘者对TypeScript类型推...
- 怀旧服实用宏整理,猎人篇(怀旧服实用宏整理,猎人篇怎么用)
-
关于宏的贴子不少,这里我去芜存菁,整理并留下了觉得比较实用的宏命令,希望对大家有用。/m打开宏命令设置窗口宝宝清图腾宏/scriptlocalt,n,i,_={"根基\","...
- MS15-083:Windows SMB内存损坏漏洞分析
-
2015年8月11日微软发布了14个安全补丁,其中就包括一个SMB服务器补丁。在本文我将解释我是如何触发该漏洞的。微软安全公告MS15-083在所有的修复补丁中,我对“服务器消息块中的漏洞可能允许远程...
- 一周热门
- 最近发表
-
- C#.NET NLog 详解(c#nuget)
- WPF中datagrid单元格背景颜色(wpf datagrid单元格编辑)
- Unity Profiler实战指南:从卡顿到丝滑的性能优化之旅
- Windows下取文件属性特例(windows文件属性快捷键)
- 展开说说,DOS有哪些常用、实用的命令?
- webservice更改返回信息节点名称(webservice返回值)
- Windows Server 2019 基线检查表 (1)
- MapStruct架构设计(mapstruct官方文档)
- Excel常用技能分享与探讨(6-实战小功能分享 三)
- 30天学会Python编程:8. Python面向对象编程
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- HTML button formtarget 属性 (30)
- CSS 水平对齐 (Horizontal Align) (30)
- opacity 属性 (32)