干掉 BeanUtils!试试这款 Bean 自动映射工具,真心强大!
平时做项目的时候,经常需要做PO、VO、DTO之间的转换。简单的对象转换,使用BeanUtils基本上是够了,但是复杂的转换,如果使用它的话又得写一堆Getter、Setter方法了。今天给大家推荐一款对象自动映射工具
MapStruct
,功能真心强大!
关于BeanUtils
平时我经常使用Hutool中的BeanUtil类来实现对象转换,用多了之后就发现有些缺点:
对象属性映射使用反射来实现,性能比较低;
对于不同名称或不同类型的属性无法转换,还得单独写Getter、Setter方法;
对于嵌套的子对象也需要转换的情况,也得自行处理;
集合对象转换时,得使用循环,一个个拷贝。
对于这些不足,MapStruct都能解决,不愧为一款功能强大的对象映射工具!
MapStruct简介
MapStruct是一款基于Java注解的对象属性映射工具,在Github上已经有4.5K+Star。使用的时候我们只要在接口中定义好对象属性映射规则,它就能自动生成映射实现类,不使用反射,性能优秀,能实现各种复杂映射。
IDEA插件支持
作为一款非常流行的对象映射工具,MapStruct还提供了专门的IDEA插件,我们在使用之前可以先安装好插件。
项目集成
在SpingBoot中集成MapStruct非常简单,仅续添加如下两个依赖即可,这里使用的是
1.4.2.Final
版本。
<dependency><!--MapStruct相关依赖--><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>${mapstruct.version}</version></dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${mapstruct.version}</version><scope>compile</scope></dependency>
</dependencies>
基本使用
集成完MapStruct之后,我们来体验下它的功能吧,看看它有何神奇之处!
基本映射
我们先来个快速入门,体验一下MapStruct的基本功能,并聊聊它的实现原理。
首先我们准备好要使用的会员PO对象
Member
;
/*** 购物会员* Created by macro on 2021/10/12.*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Member {private Long id;private String username;private String password;private String nickname;private Date birthday;private String phone;private String icon;private Integer gender;
}
然后再准备好会员的DTO对象
MemberDto
,我们需要将Member
对象转换为MemberDto
对象;
/*** 购物会员Dto* Created by macro on 2021/10/12.*/
@Data
@EqualsAndHashCode(callSuper = false)
public class MemberDto {private Long id;private String username;private String password;private String nickname;//与PO类型不同的属性private String birthday;//与PO名称不同的属性private String phoneNumber;private String icon;private Integer gender;
}
然后创建一个映射接口
MemberMapper
,实现同名同类型属性、不同名称属性、不同类型属性的映射;
/*** 会员对象映射* Created by macro on 2021/10/21.*/
@Mapper
public interface MemberMapper {MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);@Mapping(source = "phone",target = "phoneNumber")@Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")MemberDto toDto(Member member);
}
接下来在Controller中创建测试接口,直接通过接口中的
INSTANCE
实例调用转换方法toDto
;
/*** MapStruct对象转换测试Controller* Created by macro on 2021/10/21.*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {@ApiOperation(value = "基本映射")@GetMapping("/baseMapping")public CommonResult baseTest() {List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);MemberDto memberDto = MemberMapper.INSTANCE.toDto(memberList.get(0));return CommonResult.success(memberDto);}
}
运行项目后在Swagger中测试接口,发现PO所有属性已经成功转换到DTO中去了,Swagger访问地址:http://localhost:8088/swagger-ui
其实MapStruct的实现原理很简单,就是根据我们在Mapper接口中使用的
@Mapper
和@Mapping
等注解,在运行时生成接口的实现类,我们可以打开项目的target
目录看下;
下面是MapStruct为
MemberMapper
生成好的对象映射代码,可以和手写Getter、Setter说再见了!
public class MemberMapperImpl implements MemberMapper {public MemberMapperImpl() {}public MemberDto toDto(Member member) {if (member == null) {return null;} else {MemberDto memberDto = new MemberDto();memberDto.setPhoneNumber(member.getPhone());if (member.getBirthday() != null) {memberDto.setBirthday((new SimpleDateFormat("yyyy-MM-dd")).format(member.getBirthday()));}memberDto.setId(member.getId());memberDto.setUsername(member.getUsername());memberDto.setPassword(member.getPassword());memberDto.setNickname(member.getNickname());memberDto.setIcon(member.getIcon());memberDto.setGender(member.getGender());return memberDto;}}
}
集合映射
MapStruct也提供了集合映射的功能,可以直接将一个PO列表转换为一个DTO列表,再也不用一个个对象转换了!
在
MemberMapper
接口中添加toDtoList
方法用于列表转换;
/*** 会员对象映射* Created by macro on 2021/10/21.*/
@Mapper
public interface MemberMapper {MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);@Mapping(source = "phone",target = "phoneNumber")@Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")List<MemberDto> toDtoList(List<Member> list);
}
在Controller中创建测试接口,直接通过Mapper接口中的
INSTANCE
实例调用转换方法toDtoList
;
/*** MapStruct对象转换测试Controller* Created by macro on 2021/10/21.*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {@ApiOperation(value = "集合映射")@GetMapping("/collectionMapping")public CommonResult collectionMapping() {List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);List<MemberDto> memberDtoList = MemberMapper.INSTANCE.toDtoList(memberList);return CommonResult.success(memberDtoList);}
}
在Swagger中调用接口测试下,PO列表已经转换为DTO列表了。
子对象映射
MapStruct对于对象中包含子对象也需要转换的情况也是有所支持的。
例如我们有一个订单PO对象
Order
,嵌套有Member
和Product
对象;
/*** 订单* Created by macro on 2021/10/12.*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Order {private Long id;private String orderSn;private Date createTime;private String receiverAddress;private Member member;private List<Product> productList;
}
我们需要转换为
OrderDto
对象,OrderDto
中包含MemberDto
和ProductDto
两个子对象同样需要转换;
/*** 订单Dto* Created by macro on 2021/10/12.*/
@Data
@EqualsAndHashCode(callSuper = false)
public class OrderDto {private Long id;private String orderSn;private Date createTime;private String receiverAddress;//子对象映射Dtoprivate MemberDto memberDto;//子对象数组映射Dtoprivate List<ProductDto> productDtoList;
}
我们只需要创建一个Mapper接口,然后通过使用
uses
将子对象的转换Mapper注入进来,然后通过@Mapping
设置好属性映射规则即可;
/*** 订单对象映射* Created by macro on 2021/10/21.*/
@Mapper(uses = {MemberMapper.class,ProductMapper.class})
public interface OrderMapper {OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);@Mapping(source = "member",target = "memberDto")@Mapping(source = "productList",target = "productDtoList")OrderDto toDto(Order order);
}
接下来在Controller中创建测试接口,直接通过Mapper中的
INSTANCE
实例调用转换方法toDto
;
/*** MapStruct对象转换测试Controller* Created by macro on 2021/10/21.*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {@ApiOperation(value = "子对象映射")@GetMapping("/subMapping")public CommonResult subMapping() {List<Order> orderList = getOrderList();OrderDto orderDto = OrderMapper.INSTANCE.toDto(orderList.get(0));return CommonResult.success(orderDto);}
}
在Swagger中调用接口测试下,可以发现子对象属性已经被转换了。
合并映射
MapStruct也支持把多个对象属性映射到一个对象中去。
例如这里把
Member
和Order
的部分属性映射到MemberOrderDto
中去;
/*** 会员商品信息组合Dto* Created by macro on 2021/10/21.*/
@Data
@EqualsAndHashCode(callSuper = false)
public class MemberOrderDto extends MemberDto{private String orderSn;private String receiverAddress;
}
然后在Mapper中添加
toMemberOrderDto
方法,这里需要注意的是由于参数中具有两个属性,需要通过参数名称.属性
的名称来指定source
来防止冲突(这两个参数中都有id属性);
/*** 会员对象映射* Created by macro on 2021/10/21.*/
@Mapper
public interface MemberMapper {MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);@Mapping(source = "member.phone",target = "phoneNumber")@Mapping(source = "member.birthday",target = "birthday",dateFormat = "yyyy-MM-dd")@Mapping(source = "member.id",target = "id")@Mapping(source = "order.orderSn", target = "orderSn")@Mapping(source = "order.receiverAddress", target = "receiverAddress")MemberOrderDto toMemberOrderDto(Member member, Order order);
}
接下来在Controller中创建测试接口,直接通过Mapper中的
INSTANCE
实例调用转换方法toMemberOrderDto
;
/*** MapStruct对象转换测试Controller* Created by macro on 2021/10/21.*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {@ApiOperation(value = "组合映射")@GetMapping("/compositeMapping")public CommonResult compositeMapping() {List<Order> orderList = LocalJsonUtil.getListFromJson("json/orders.json", Order.class);List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);Member member = memberList.get(0);Order order = orderList.get(0);MemberOrderDto memberOrderDto = MemberMapper.INSTANCE.toMemberOrderDto(member,order);return CommonResult.success(memberOrderDto);}
}
在Swagger中调用接口测试下,可以发现Member和Order中的属性已经被映射到MemberOrderDto中去了。
进阶使用
通过上面的基本使用,大家已经可以玩转MapStruct了,下面我们再来介绍一些进阶的用法。
使用依赖注入
上面我们都是通过Mapper接口中的INSTANCE实例来调用方法的,在Spring中我们也是可以使用依赖注入的。
想要使用依赖注入,我们只要将
@Mapper
注解的componentModel
参数设置为spring
即可,这样在生成接口实现类时,MapperStruct会为其添加@Component
注解;
/*** 会员对象映射(依赖注入)* Created by macro on 2021/10/21.*/
@Mapper(componentModel = "spring")
public interface MemberSpringMapper {@Mapping(source = "phone",target = "phoneNumber")@Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")MemberDto toDto(Member member);
}
接下来在Controller中使用
@Autowired
注解注入即可使用;
/*** MapStruct对象转换测试Controller* Created by macro on 2021/10/21.*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {@Autowiredprivate MemberSpringMapper memberSpringMapper;@ApiOperation(value = "使用依赖注入")@GetMapping("/springMapping")public CommonResult springMapping() {List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);MemberDto memberDto = memberSpringMapper.toDto(memberList.get(0));return CommonResult.success(memberDto);}
}
在Swagger中调用接口测试下,可以发现与之前一样可以正常使用。
使用常量、默认值和表达式
使用MapStruct映射属性时,我们可以设置属性为常量或者默认值,也可以通过Java中的方法编写表达式来自动生成属性。
例如下面这个商品类Product对象;
/*** 商品* Created by macro on 2021/10/12.*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Product {private Long id;private String productSn;private String name;private String subTitle;private String brandName;private BigDecimal price;private Integer count;private Date createTime;
}
我们想把Product转换为ProductDto对象,
id
属性设置为常量,count
设置默认值为1,productSn
设置为UUID生成;
/*** 商品Dto* Created by macro on 2021/10/12.*/
@Data
@EqualsAndHashCode(callSuper = false)
public class ProductDto {//使用常量private Long id;//使用表达式生成属性private String productSn;private String name;private String subTitle;private String brandName;private BigDecimal price;//使用默认值private Integer count;private Date createTime;
}
创建
ProductMapper
接口,通过@Mapping
注解中的constant
、defaultValue
、expression
设置好映射规则;
/*** 商品对象映射* Created by macro on 2021/10/21.*/
@Mapper(imports = {UUID.class})
public interface ProductMapper {ProductMapper INSTANCE = Mappers.getMapper(ProductMapper.class);@Mapping(target = "id",constant = "-1L")@Mapping(source = "count",target = "count",defaultValue = "1")@Mapping(target = "productSn",expression = "java(UUID.randomUUID().toString())")ProductDto toDto(Product product);
}
接下来在Controller中创建测试接口,直接通过接口中的
INSTANCE
实例调用转换方法toDto
;
/*** MapStruct对象转换测试Controller* Created by macro on 2021/10/21.*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {@ApiOperation(value = "使用常量、默认值和表达式")@GetMapping("/defaultMapping")public CommonResult defaultMapping() {List<Product> productList = LocalJsonUtil.getListFromJson("json/products.json", Product.class);Product product = productList.get(0);product.setId(100L);product.setCount(null);ProductDto productDto = ProductMapper.INSTANCE.toDto(product);return CommonResult.success(productDto);}
}
在Swagger中调用接口测试下,对象已经成功转换。
在映射前后进行自定义处理
MapStruct也支持在映射前后做一些自定义操作,类似AOP中的切面。
由于此时我们需要创建自定义处理方法,创建一个抽象类
ProductRoundMapper
,通过@BeforeMapping
注解自定义映射前操作,通过@AfterMapping
注解自定义映射后操作;
/*** 商品对象映射(自定义处理)* Created by macro on 2021/10/21.*/
@Mapper(imports = {UUID.class})
public abstract class ProductRoundMapper {public static ProductRoundMapper INSTANCE = Mappers.getMapper(ProductRoundMapper.class);@Mapping(target = "id",constant = "-1L")@Mapping(source = "count",target = "count",defaultValue = "1")@Mapping(target = "productSn",expression = "java(UUID.randomUUID().toString())")public abstract ProductDto toDto(Product product);@BeforeMappingpublic void beforeMapping(Product product){//映射前当price<0时设置为0if(product.getPrice().compareTo(BigDecimal.ZERO)<0){product.setPrice(BigDecimal.ZERO);}}@AfterMappingpublic void afterMapping(@MappingTarget ProductDto productDto){//映射后设置当前时间为createTimeproductDto.setCreateTime(new Date());}
}
接下来在Controller中创建测试接口,直接通过Mapper中的
INSTANCE
实例调用转换方法toDto
;
/*** MapStruct对象转换测试Controller* Created by macro on 2021/10/21.*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {@ApiOperation(value = "在映射前后进行自定义处理")@GetMapping("/customRoundMapping")public CommonResult customRoundMapping() {List<Product> productList = LocalJsonUtil.getListFromJson("json/products.json", Product.class);Product product = productList.get(0);product.setPrice(new BigDecimal(-1));ProductDto productDto = ProductRoundMapper.INSTANCE.toDto(product);return CommonResult.success(productDto);}
}
在Swagger中调用接口测试下,可以发现已经应用了自定义操作。
处理映射异常
代码运行难免会出现异常,MapStruct也支持处理映射异常。
我们需要先创建一个自定义异常类;
/*** 商品验证异常类* Created by macro on 2021/10/22.*/
public class ProductValidatorException extends Exception{public ProductValidatorException(String message) {super(message);}
}
然后创建一个验证类,当
price
设置小于0
时抛出我们自定义的异常;
/*** 商品验证异常处理器* Created by macro on 2021/10/22.*/
public class ProductValidator {public BigDecimal validatePrice(BigDecimal price) throws ProductValidatorException {if(price.compareTo(BigDecimal.ZERO)<0){throw new ProductValidatorException("价格不能小于0!");}return price;}
}
之后我们通过
@Mapper
注解的uses
属性运用验证类;
/*** 商品对象映射(处理映射异常)* Created by macro on 2021/10/21.*/
@Mapper(uses = {ProductValidator.class},imports = {UUID.class})
public interface ProductExceptionMapper {ProductExceptionMapper INSTANCE = Mappers.getMapper(ProductExceptionMapper.class);@Mapping(target = "id",constant = "-1L")@Mapping(source = "count",target = "count",defaultValue = "1")@Mapping(target = "productSn",expression = "java(UUID.randomUUID().toString())")ProductDto toDto(Product product) throws ProductValidatorException;
}
然后在Controller中添加测试接口,设置
price
为-1
,此时在进行映射时会抛出异常;
/*** MapStruct对象转换测试Controller* Created by macro on 2021/10/21.*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {@ApiOperation(value = "处理映射异常")@GetMapping("/exceptionMapping")public CommonResult exceptionMapping() {List<Product> productList = LocalJsonUtil.getListFromJson("json/products.json", Product.class);Product product = productList.get(0);product.setPrice(new BigDecimal(-1));ProductDto productDto = null;try {productDto = ProductExceptionMapper.INSTANCE.toDto(product);} catch (ProductValidatorException e) {e.printStackTrace();}return CommonResult.success(productDto);}
}
在Swagger中调用接口测试下,发现运行日志中已经打印了自定义异常信息。
总结
通过上面对MapStruct的使用体验,我们可以发现MapStruct远比BeanUtils要强大。当我们想实现比较复杂的对象映射时,通过它可以省去写Getter、Setter方法的过程。当然上面只是介绍了MapStruct的一些常用功能,它的功能远不止于此,感兴趣的朋友可以查看下官方文档。
参考资料
官方文档:https://mapstruct.org/documentation/stable/reference/html
项目源码地址
https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-mapstruct
有道无术,术可成;有术无道,止于术
欢迎大家关注Java之道公众号
好文章,我在看❤️
干掉 BeanUtils!试试这款 Bean 自动映射工具,真心强大!相关推荐
- 被垃圾分类逼疯?试试这款垃圾自动分类器
让垃圾自动分类 近期垃圾分类成为了一个热门话题,原来直接一次性扔掉的垃圾,现在都需要分门别类进行投放.从今年7月1日起,新的<上海市生活垃圾管理条例>正式开始施行,号称史上最严的垃圾分类就 ...
- 让你收入翻倍,做自媒体不会写文案?用这3款文案自动生成工具
写文案是不是让你很头疼?现在都2021年了,你还在自己写文案? 对于新手小白来说,做自媒体写文案是一件让人非常头疼的事情. 今天这期内容就来给大家分享几个我自己常用的辅助工具,可以帮你自动生成文案. ...
- 图片如何直接编辑?快来试试这款图片在线处理工具
作为一名新媒体工作者,在日常工作中经常需要做图片处理的工作,比如图片尺寸修改.压缩图片大小.图片编辑美化等等,那么出除了使用图片编辑软件之外,还有没有什么简单的方法可以快速编辑图片呢?今天就来给大家分 ...
- automapper自动创建映射_AutoMapper 自动映射工具
11.转换匿名对象 结合LINQ映射新的实体类. using System; using System.Collections.Generic; using System.Linq; using Sy ...
- Gif动画怎么在线制作?快试试这款gif在线制作工具
Gif动画图片怎么制作呢?使用[GIF中文网]的gif动画制作(https://www.gif.cn/)功能.上传jpg.png以及gif格式的图片,一键就可以在线合成gif图片,简单方便容易操作.还 ...
- html自动生成工具_关于STM32代码自动生成的工具的进度....
前情提要:STM32代码自动生成工具_本想...但是...可是...所以 首先说一下那几天大家的反应,有的持观望态度,毕竟STM32CUBE很香:有的很激动,期待我快点出东西:还有的很淡定,知道我在挖 ...
- 探探自动配对PHP_探探自动匹配工具app下载-探探自动匹配工具(好友匹配)app安卓版v1.0-电玩之家...
探探自动匹配工具是一款自动匹配添加好友的应用软件,探探自动匹配工具支持定时刷新好友功能,支持搜索匹配符合的用户,自动添加好友.对于有这个需要的用户来说是款不错的软件.探探自动匹配工具功能强大.操作简单 ...
- java orika_常见Bean映射工具分析评测及Orika介绍
Bean映射工具选择 工作中,我们经常需要将对象转换成不同的形式以适应不同的api,或者在不同业务层中传输对象而不同分层的对象存在不同的格式,因此我们需要编写映射代码将对象中的属性值从一种类型转换成另 ...
- Spring Boot 注解配置文件自动映射到属性和实体类
官网给出的配置文件大全: https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#comm ...
最新文章
- java arraylist 函数_使用Java-8中的函数ArrayList
- odoo10学习笔记十四:mixin其他功能模块
- Hadoop2.6.0中YARN底层状态机实现分析
- FoundationDB 学习 - 事务流程
- Git 与 GitHub 速成教程
- dqkg的命令用法_CADk中常用命令使用方法及说明
- Project:圆柱滚子轴承接触表面应力计算——GB T18254-2002高碳铬轴承钢
- Excel汇总大师破解版 v1.6.8 免安装绿色版 Excel合并拆分
- 富爸爸穷爸爸(读后感悟,书中重点)
- 怎样用键盘控制电脑的光标
- 从NCBI 上下载 gbff 文件并得到 CDS 信息
- 上海财经大学如何构建量化高频数据中心?
- Linux实战(一):服务器应用迁移
- Linux内存池技术
- winform中TreeView显示树状图
- 论光模块光电信号的转换和信号的调制
- 怎么申请沙特SABER认证及流程如何
- 如何搭建私有部署企业网盘
- NGINX按天生成日志文件的简易配置
- Java后端大数据 从0到1学习路线分析与规划
热门文章
- mysql blob图片类型存储bug解决:索引超出了数组界限错误_索引超出了数组界限(Microsoft.SqlServer.Smo)...
- python file read和write的速度_python file.truncate() 然后 file.write() file.read() 出现乱码...
- oracle truncate 日期,【Oracle】truncate和delete区别
- 学python必会英语单词_Python必备常用英语词汇(一)
- getting joins
- 关于android开源类库StickyListHeaderAdapter 的写法注意
- 计算机网络之数据链路层:6、后退N帧协议(GBN)
- (计算机组成原理)第五章中央处理器-第四节4:微程序控制单元设计
- Linux系统编程27:进程间通信之管道的基本概念和匿名管道与命名管道及管道特性
- windows下使用net-snmp实现agent扩展(四)