前几天,远在北京的小伙伴在群里抛出了 “MapStruct”
的概念。对于只闻其名,未见其人的我来说,决定对其研究一番。本文我们就从 MapStruct
的概念出发,通过具体的代码示例来研究它的使用情况,最后与”市面上”的其它工具来做个对比!
官方介绍
首先我们打开 MapStruct
的官网地址,映入眼帘的就是下边的三步曲:
What is it?
MapStruct
是一个 代码生成器,它基于约定优先于配置的方法大大简化了 JavaBean
类型之间映射的实现。生成的映射代码使用 普通方法调用,因此速度快、类型 安全且易于理解。
Why?
多层应用程序通常需要在不同的对象模型(例如实体和 DTO
)之间进行 映射。编写这样的映射代码是一项乏味且容易出错的任务。 MapStruct
旨在通过尽可能自动化来简化这项工作。
与其他映射框架不同, MapStruct
在 编译时生成 bean
映射,这确保了高性能,允许快速的开发人员反馈和彻底的错误检查。
How?
MapStruct
是插入 Java
编译器的 注释处理器,可以在命令行构建( Maven
、 Gradle
等)中使用,也可以在首选 IDE
中使用。它使用合理的默认值,但在配置或实现特殊行为时,用户可以自定义实现。
官网的解释总是咬文嚼字,晦涩难懂的,看到这你只需要记住 MapStruct
是用来做实体类映射——实体类拷贝 的就可以了。
源码地址:https://github.com/mapstruct/mapstruct
官网推荐的 Demo: https://github.com/mapstruct/mapstruct-examples
简单实现
我们注意到官网中有涉及到简单样例的实现,我们用2分钟来分析一波:
1. 引入依赖
org.mapstruct
mapstruct-jdk8
1.3.0.Final
//注解处理器,根据注解自动生成mapper的实现
org.mapstruct
mapstruct-processor
1.2.0.Final
我们在编译时会报
java: No property named "numberOfSeats" exists in source parameter(s). Did you mean "null"?
错误,经过查阅资料发现mapstruct-processor
和Lombok
的版本需要统一一下:mapstruct-processor
:1.2.0.Final
,Lombok
:1.16.14
。
2. 准备实体类 Car.java
和 数据传输类 CarDto.java
<br><br><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Car</span> </span>{<br>    <span class="hljs-keyword">private</span> String make;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> numberOfSeats;<br>    <span class="hljs-keyword">private</span> CarType type;<br>}<br><br><br><br><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CarDto</span> </span>{<br>    <span class="hljs-keyword">private</span> String make;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> seatCount;<br>    <span class="hljs-keyword">private</span> String type;<br><br>}
3. 创建映射器接口,里边定义映射方法
<br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">CarMapper</span> </span>{<br> <br>    CarMapper INSTANCE = Mappers.getMapper( CarMapper<span class="hljs-class">.<span class="hljs-keyword">class</span> )</span>;<br><br>    (source = <span class="hljs-string">"numberOfSeats"</span>, target = <span class="hljs-string">"seatCount"</span>)<br>    <span class="hljs-function">CarDto <span class="hljs-title">carToCarDto</span><span class="hljs-params">(Car car)</span></span>; <br>   <br>}
解析分析:
@Mapper
将接口标记为映射接口,并允许MapStruct
处理器在编译期间启动。这里的@Mapper
注解不是mybatis
的注解,而是org.mapstruct.Mapper
的;- 实际映射方法
carToCarDto()
期望源对象Car
作为参数,并返回目标对象CarDto
,方法名可以自由选择; - 对于源对象和目标对象中具有 不同名称 的属性,可以使用
@Mapping
注释来配置名称; - 对于源对象和目标对象中具有 不同类型 的属性,也可以使用
@Mapping
注释来进行转换,比如:类型属性将从枚举类型转换为字符串; - 一个接口中可以有多个映射方法,对于所有的这些方法,
MapStruct
将生成一个实现; - 该接口的实现实例可以从
Mappers
中获得,接口声明一个INSTANCE
,为客户端提供对映射器实现的访问。
4. 实现类
从代码中可以看出 MapStruct
为我们自动生成了 set/get
代码,并且对 枚举类进行了特殊处理。
5. 客户端
<br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">shouldMapCarToDto</span><span class="hljs-params">()</span> </span>{<br><br>    Car car = <span class="hljs-keyword">new</span> Car( <span class="hljs-string">"Morris"</span>, <span class="hljs-number">5</span>, CarType.SEDAN );<br>    CarDto carDto = CarMapper.INSTANCE.carToCarDto( car );<br>    System.out.println(carDto);<br>    <br>}
小结:
MapStruct
基于mapper
接口,在 编译期动态生成set/get
代码的class
文件 ,在运行时直接调用该class
文件。
MapStruct 配置
@Mapper
我们翻开上边提到的 Mapper
注释的源码,该注释的解释是:将接口或抽象类标记为 映射器,并通过 MapStruct
激活 该类型实现的生成。我们找到其中的 componentModel 属性,默认值为 default
,它有四种值供我们选择:
- default:映射器不使用组件模型,实例通常通过
Mappers.getMapper(java.lang.Class)
获取; - cdi:生成的映射器是
application-scoped
的CDI bean
,可以通过@Inject
获取; - spring:生成的映射器是
Spring bean
,可以通过@Autowired
获取; - jsr330:生成的映射器被
@javax.inject.Named
和@Singleton
注释,可以通过@inject
获取;
上边我们用的就是默认的方法,当然我们也可以用 @Autowired
来引入接口依赖,此处不再举例,有兴趣的小伙伴可以自己试试!
另外我们可以看下 uses
属性:可以通过定义其他类来完成字段转换,接下来我们来个小例子演示一下:
1. 定义一个 CarVo.java 类
<br><br><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CarVo</span> </span>{<br><br>    <span class="hljs-keyword">private</span> String make;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> seatCount;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">boolean</span> type;<br>}
2. 在 mapper 中定义一个 vo 转为 dto 的方法 CarDto carVoToCarDto(CarVo carVo);
当不加 uses
属性时,查看编译后生成的实现类
<span class="hljs-function"><span class="hljs-keyword">public</span> CarDto <span class="hljs-title">carVoToCarDto</span><span class="hljs-params">(CarVo carVo)</span> </span>{<br> <span class="hljs-keyword">if</span> (carVo == <span class="hljs-keyword">null</span>) {<br>  <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;<br> } <span class="hljs-keyword">else</span> {<br>  CarDto carDto = <span class="hljs-keyword">new</span> CarDto();<br>  carDto.setMake(carVo.getMake());<br>  carDto.setSeatCount(carVo.getSeatCount());<br>  carDto.setType(String.valueOf(carVo.isType()));<br>  <span class="hljs-keyword">return</span> carDto;<br> }<br>}
- 在
mapper
上增加uses
属性,并指定自定义的处理类,代码如下:
(uses = {BooleanStrFormat<span class="hljs-class">.<span class="hljs-keyword">class</span>})<br><span class="hljs-title">public</span> <span class="hljs-title">interface</span> <span class="hljs-title">CarMapper</span> </span>{<br>    ......<br>}<br><br><br><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BooleanStrFormat</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">toStr</span><span class="hljs-params">(<span class="hljs-keyword">boolean</span> type)</span> </span>{<br>        <span class="hljs-keyword">if</span>(type){<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">"Y"</span>;<br>        }<span class="hljs-keyword">else</span>{<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">"N"</span>;<br>        }<br>    }<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">toBoolean</span><span class="hljs-params">(String type)</span> </span>{<br>        <span class="hljs-keyword">if</span> (type.equals(<span class="hljs-string">"Y"</span>)) {<br>            <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;<br>        } <span class="hljs-keyword">else</span> {<br>            <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;<br>        }<br>    }<br>}<br><br><br><span class="hljs-function"><span class="hljs-keyword">public</span> CarDto <span class="hljs-title">carVoToCarDto</span><span class="hljs-params">(CarVo carVo)</span> </span>{<br> <span class="hljs-keyword">if</span> (carVo == <span class="hljs-keyword">null</span>) {<br>  <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;<br> } <span class="hljs-keyword">else</span> {<br>  CarDto carDto = <span class="hljs-keyword">new</span> CarDto();<br>  carDto.setMake(carVo.getMake());<br>  carDto.setSeatCount(carVo.getSeatCount());<br>        <br>  carDto.setType(<span class="hljs-keyword">this</span>.booleanStrFormat.toStr(carVo.isType()));<br>  <span class="hljs-keyword">return</span> carDto;<br> }<br>}
4.客户端代码
<br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">shouldMapCarVoToDto</span><span class="hljs-params">()</span> </span>{<br><br> CarVo carVo = <span class="hljs-keyword">new</span> CarVo( <span class="hljs-string">"Morris"</span>, <span class="hljs-number">5</span>, <span class="hljs-keyword">false</span> );<br> CarDto carDto = CarMapper.INSTANCE.carVoToCarDto( carVo );<br><br> System.out.println(carDto);<br>}
@Mapping
@Mapping
可以用来配置一个 bean
属性或枚举常量的映射,默认是将具有相同名称的属性进行映射,当然也可以用 source
、 expression
或者 constant
属性手动指定,接下来我们来分析下常用的属性值。
- target:属性的目标名称,同一目标属性不能映射多次。如果用于映射枚举常量,则将给出常量成员的名称,在这种情况下,源枚举中的多个值可以映射到目标枚举的相同值。
-
source:属性的源名称,
-
如果带注释的方法有多个源参数,则属性名称必须使用参数名称限定,例如
“addressParam.city"
; - 当找不到匹配的属性时,
MapStruct
将查找匹配的参数名称; - 当用于映射枚举常量时,将给出常量成员的名称;
-
该属性不能与
constant
或expression
一起使用; -
dateFormat:通过
SimpleDateFormat
实现String
到Date
日期之间相互转换。 - numberFormat:通过
DecimalFormat
实现Number
与String
的数值格式化。 - constant:设置指定目标属性的常量字符串,当指定的目标属性的类型为:
primitive
或boxed
(例如Long
)时,MapStruct
检查是否可以将该primitive
作为有效的文本分配给primitive
或boxed
类型。如果可能,MapStruct
将分配为文字;如果不可能,MapStruct
将尝试应用用户定义的映射方法。 另外,MapStruct
将常量作为字符串处理,将通过应用匹配方法、类型转换方法或内置转换来转换该值。此属性不能与source
、defaultValue
、defaultExpression
或expression
一起使用。 - expression:是一个表达式,根据该表达式设置指定的目标属性。他的属性不能与
source
、defaultValue
、defaultExpression
、constant
一起使用。 - ignore: 忽略这个字段。
我们用 expression
这个属性来实现一下上边用 uses
实现的案例:
1. 在 mapper 中定义方法
(target = <span class="hljs-string">"type"</span>, expression = <span class="hljs-string">"java(new com.ittest.controller.BooleanStrFormat().toStr(carVo.isType()))"</span>)<br><span class="hljs-function">CarDto <span class="hljs-title">carVoToDtoWithExpression</span><span class="hljs-params">(CarVo carVo)</span></span>;
2. 生成的实现类
<br><span class="hljs-function"><span class="hljs-keyword">public</span> CarDto <span class="hljs-title">carVoToDtoWithExpression</span><span class="hljs-params">(CarVo carVo)</span> </span>{<br> <span class="hljs-keyword">if</span> ( carVo == <span class="hljs-keyword">null</span> ) {<br>  <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;<br> }<br><br> CarDto carDto = <span class="hljs-keyword">new</span> CarDto();<br><br> carDto.setMake( carVo.getMake() );<br> carDto.setSeatCount( carVo.getSeatCount() );<br><br> carDto.setType( <span class="hljs-keyword">new</span> com.ittest.controller.BooleanStrFormat().toStr(carVo.isType()) );<br><br> <span class="hljs-keyword">return</span> carDto;<br>}
3. 客户端
<br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">mapCarVoToDtoWithExpression</span><span class="hljs-params">()</span> </span>{<br><br> CarVo carVo = <span class="hljs-keyword">new</span> CarVo( <span class="hljs-string">"Morris"</span>, <span class="hljs-number">5</span>, <span class="hljs-keyword">false</span> );<br> CarDto carDto = CarMapper.INSTANCE.carVoToDtoWithExpression( carVo );<br><br> System.out.println(carDto);<br>}
至于其他的用法大家可以多多探索。
重要提示:枚举映射功能已被弃用,并被
ValueMapping
取代。它将在后续版本中删除。
@Mappings
可以配置多个 @Mapping
,例如
({<br>    (source = <span class="hljs-string">"id"</span>, target = <span class="hljs-string">"carId"</span>),<br>    (source = <span class="hljs-string">"name"</span>, target = <span class="hljs-string">"carName"</span>),<br>    (source = <span class="hljs-string">"color"</span>, target = <span class="hljs-string">"carColor"</span>)<br>})
@MappingTarget
用于更新已有对象,还是用例子来说明吧:
1. 创建 BMWCar.java 类
<br><br><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BMWCar</span> </span>{<br>    <span class="hljs-keyword">private</span> String make;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> numberOfSeats;<br>    <span class="hljs-keyword">private</span> CarType type;<br><br>    <span class="hljs-keyword">private</span> String color;<br>    <span class="hljs-keyword">private</span> String price;<br><br>}
2. mapper 中创建更新方法,并查看实现类
<br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">updateBwmCar</span><span class="hljs-params">(Car car, @MappingTarget BMWCar bwmCar)</span></span>;<br><br><br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">updateBwmCar</span><span class="hljs-params">(Car car, BMWCar bwmCar)</span> </span>{<br> <span class="hljs-keyword">if</span> (car != <span class="hljs-keyword">null</span>) {<br>  bwmCar.setMake(car.getMake());<br>  bwmCar.setNumberOfSeats(car.getNumberOfSeats());<br>  bwmCar.setType(car.getType());<br> }<br>}
3. 客户端代码
<br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">updateBwmCar</span><span class="hljs-params">()</span> </span>{<br> Car car = <span class="hljs-keyword">new</span> Car( <span class="hljs-string">"Morris"</span>, <span class="hljs-number">5</span>, CarType.SEDAN );<br> BMWCar bwmCar = <span class="hljs-keyword">new</span> BMWCar(<span class="hljs-string">"BWM"</span>, <span class="hljs-number">5</span>, CarType.SPORTS, <span class="hljs-string">"RED"</span>, <span class="hljs-string">"50w"</span>);<br> System.out.println(<span class="hljs-string">"更新前 car:"</span>+car.toString());<br> System.out.println(<span class="hljs-string">"更新前 BWMCar:"</span>+bwmCar.toString());<br><br> CarMapper.INSTANCE.updateBwmCar(car, bwmCar);<br><br> System.out.println(<span class="hljs-string">"更新后 car:"</span>+car.toString());<br> System.out.println(<span class="hljs-string">"更新后 BWMCar:"</span>+bwmCar.toString());<br>}
扩展:多个对象映射一个对象
1. 准备实体类 Benz4SMall.java
和 Mall4S.java
<br><br><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Mall4S</span> </span>{<br><br>    <span class="hljs-keyword">private</span> String address;<br><br>    <span class="hljs-keyword">private</span> String mobile;<br><br>}<br><br><br><br><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Benz4SMall</span> </span>{<br><br>    <span class="hljs-keyword">private</span> String address;<br>    <span class="hljs-keyword">private</span> String mobile;<br>    <span class="hljs-keyword">private</span> String make;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> numberOfSeats;<br>}
2. mapper 创建转换方法并查看生成的实现类
<span class="hljs-function">Benz4SMall <span class="hljs-title">mallCarToBenzMall</span><span class="hljs-params">(Car car, Mall4S mall4S)</span></span>;<br><br><br><span class="hljs-function"><span class="hljs-keyword">public</span> Benz4SMall <span class="hljs-title">mallCarToBenzMall</span><span class="hljs-params">(Car car, Mall4S mall4S)</span> </span>{<br> <span class="hljs-keyword">if</span> (car == <span class="hljs-keyword">null</span> && mall4S == <span class="hljs-keyword">null</span>) {<br>  <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;<br> } <span class="hljs-keyword">else</span> {<br>  Benz4SMall benz4SMall = <span class="hljs-keyword">new</span> Benz4SMall();<br>  <span class="hljs-keyword">if</span> (car != <span class="hljs-keyword">null</span>) {<br>   benz4SMall.setMake(car.getMake());<br>   benz4SMall.setNumberOfSeats(car.getNumberOfSeats());<br>  }<br><br>  <span class="hljs-keyword">if</span> (mall4S != <span class="hljs-keyword">null</span>) {<br>   benz4SMall.setAddress(mall4S.getAddress());<br>   benz4SMall.setMobile(mall4S.getMobile());<br>  }<br><br>  <span class="hljs-keyword">return</span> benz4SMall;<br> }<br>}<br>
3. 客户端
<br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">mallCarToBenzMall</span><span class="hljs-params">()</span> </span>{<br> Car car = <span class="hljs-keyword">new</span> Car( <span class="hljs-string">"Morris"</span>, <span class="hljs-number">5</span>, CarType.SEDAN );<br> Mall4S mall4S = <span class="hljs-keyword">new</span> Mall4S(<span class="hljs-string">"北京市"</span>, <span class="hljs-string">"135XXXX4503"</span>);<br> Benz4SMall benz4SMall = CarMapper.INSTANCE.mallCarToBenzMall(car, mall4S);<br> System.out.println(benz4SMall.toString());<br>}
深拷贝与浅拷贝
深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制 实体,而不是引用。
假设 B 复制了 A ,修改 A 的时候,看 B 是否发生变化:如果 B 跟着也变了,说明是浅拷贝, 拿人手短!(修改堆内存中的同一个值);如果 B 没有改变,说明是深拷贝, 自食其力!(修改堆内存中的不同的值)
MapStruct 中是 创建新的对象,也就是 深拷贝。
MapStruct 与其他 Copy 的对比
我们在平时的项目中经常会使用到拷贝的功能,今天我们就将他们做一下对比,直接抛出 ZhaoYingChao88 大佬的实验结果:
输出结果: 手动Copy >Mapstuct>= cglibCopy > springBeanUtils > apachePropertyUtils > apacheBeanUtils
可以理解为: 手工复制 > cglib > 反射 > Dozer
。
根据测试结果,我们可以得出在速度方面, MapStruct
是最好的,执行速度是 Apache BeanUtils
的10倍、 Spring BeanUtils
的 4-5倍、和 BeanCopier
的速度差不多。
总结:在大数据量级的情况下,
MapStruct
和BeanCopier
都有着较高的性能优势,其中MapStruct
尤为优秀。如果你仅是在日常处理少量的对象时,选取哪个其实变得并不重要,但数据量大时建议还是使用MapStruct
或BeanCopier
的方式,提高接口性能。
参考链接:https://blog.csdn.net/ZYC88888/article/details/109681423?spm=1001.2014.3001.5501
回复”mapstruct”,即可获取源码呦!
以上就是今天的全部内容了,如果你有不同的意见或者更好的 idea
,欢迎联系阿Q,添加阿Q可以加入技术交流群参与讨论呦!
Original: https://www.cnblogs.com/aqsaycode/p/15407009.html
Author: 阿Q说代码
Title: 还在用BeanUtils拷贝对象?MapStruct才是王者!【附源码】
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/685479/
转载文章受原作者版权保护。转载请注明原作者出处!