平时做项目的时候,经常需要做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,嵌套有MemberProduct对象;

/*** 订单* 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中包含MemberDtoProductDto两个子对象同样需要转换;

/*** 订单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也支持把多个对象属性映射到一个对象中去。

  • 例如这里把MemberOrder的部分属性映射到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注解中的constantdefaultValueexpression设置好映射规则;

/*** 商品对象映射* 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 自动映射工具,真心强大!相关推荐

  1. 被垃圾分类逼疯?试试这款垃圾自动分类器

    让垃圾自动分类 近期垃圾分类成为了一个热门话题,原来直接一次性扔掉的垃圾,现在都需要分门别类进行投放.从今年7月1日起,新的<上海市生活垃圾管理条例>正式开始施行,号称史上最严的垃圾分类就 ...

  2. 让你收入翻倍,做自媒体不会写文案?用这3款文案自动生成工具

    写文案是不是让你很头疼?现在都2021年了,你还在自己写文案? 对于新手小白来说,做自媒体写文案是一件让人非常头疼的事情. 今天这期内容就来给大家分享几个我自己常用的辅助工具,可以帮你自动生成文案. ...

  3. 图片如何直接编辑?快来试试这款图片在线处理工具

    作为一名新媒体工作者,在日常工作中经常需要做图片处理的工作,比如图片尺寸修改.压缩图片大小.图片编辑美化等等,那么出除了使用图片编辑软件之外,还有没有什么简单的方法可以快速编辑图片呢?今天就来给大家分 ...

  4. automapper自动创建映射_AutoMapper 自动映射工具

    11.转换匿名对象 结合LINQ映射新的实体类. using System; using System.Collections.Generic; using System.Linq; using Sy ...

  5. Gif动画怎么在线制作?快试试这款gif在线制作工具

    Gif动画图片怎么制作呢?使用[GIF中文网]的gif动画制作(https://www.gif.cn/)功能.上传jpg.png以及gif格式的图片,一键就可以在线合成gif图片,简单方便容易操作.还 ...

  6. html自动生成工具_关于STM32代码自动生成的工具的进度....

    前情提要:STM32代码自动生成工具_本想...但是...可是...所以 首先说一下那几天大家的反应,有的持观望态度,毕竟STM32CUBE很香:有的很激动,期待我快点出东西:还有的很淡定,知道我在挖 ...

  7. 探探自动配对PHP_探探自动匹配工具app下载-探探自动匹配工具(好友匹配)app安卓版v1.0-电玩之家...

    探探自动匹配工具是一款自动匹配添加好友的应用软件,探探自动匹配工具支持定时刷新好友功能,支持搜索匹配符合的用户,自动添加好友.对于有这个需要的用户来说是款不错的软件.探探自动匹配工具功能强大.操作简单 ...

  8. java orika_常见Bean映射工具分析评测及Orika介绍

    Bean映射工具选择 工作中,我们经常需要将对象转换成不同的形式以适应不同的api,或者在不同业务层中传输对象而不同分层的对象存在不同的格式,因此我们需要编写映射代码将对象中的属性值从一种类型转换成另 ...

  9. Spring Boot 注解配置文件自动映射到属性和实体类

    官网给出的配置文件大全: https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#comm ...

最新文章

  1. java arraylist 函数_使用Java-8中的函数ArrayList
  2. odoo10学习笔记十四:mixin其他功能模块
  3. Hadoop2.6.0中YARN底层状态机实现分析
  4. FoundationDB 学习 - 事务流程
  5. Git 与 GitHub 速成教程
  6. dqkg的命令用法_CADk中常用命令使用方法及说明
  7. Project:圆柱滚子轴承接触表面应力计算——GB T18254-2002高碳铬轴承钢
  8. Excel汇总大师破解版 v1.6.8 免安装绿色版 Excel合并拆分
  9. 富爸爸穷爸爸(读后感悟,书中重点)
  10. 怎样用键盘控制电脑的光标
  11. 从NCBI 上下载 gbff 文件并得到 CDS 信息
  12. 上海财经大学如何构建量化高频数据中心?
  13. Linux实战(一):服务器应用迁移
  14. Linux内存池技术
  15. winform中TreeView显示树状图
  16. 论光模块光电信号的转换和信号的调制
  17. 怎么申请沙特SABER认证及流程如何
  18. 如何搭建私有部署企业网盘
  19. NGINX按天生成日志文件的简易配置
  20. Java后端大数据 从0到1学习路线分析与规划

热门文章

  1. mysql blob图片类型存储bug解决:索引超出了数组界限错误_索引超出了数组界限(Microsoft.SqlServer.Smo)...
  2. python file read和write的速度_python file.truncate() 然后 file.write() file.read() 出现乱码...
  3. oracle truncate 日期,【Oracle】truncate和delete区别
  4. 学python必会英语单词_Python必备常用英语词汇(一)
  5. getting joins
  6. 关于android开源类库StickyListHeaderAdapter 的写法注意
  7. 计算机网络之数据链路层:6、后退N帧协议(GBN)
  8. (计算机组成原理)第五章中央处理器-第四节4:微程序控制单元设计
  9. Linux系统编程27:进程间通信之管道的基本概念和匿名管道与命名管道及管道特性
  10. windows下使用net-snmp实现agent扩展(四)