干掉 BeanUtils!试试这款 Bean 自动映射工具,真心强大
yuyutoo 2024-10-29 17:29 3 浏览 0 评论
平时做项目的时候,经常需要做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;
//子对象映射Dto
private MemberDto memberDto;
//子对象数组映射Dto
private 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 {
@Autowired
private 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);
@BeforeMapping
public void beforeMapping(Product product){
//映射前当price<0时设置为0
if(product.getPrice().compareTo(BigDecimal.ZERO)<0){
product.setPrice(BigDecimal.ZERO);
}
}
@AfterMapping
public void afterMapping(@MappingTarget ProductDto productDto){
//映射后设置当前时间为createTime
productDto.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://github.com/macrozheng/mall-learning/tree/master/mall-tiny-mapstruct
来源:https://mp.weixin.qq.com/s/Yt3IIBwbgoweQnI0buSW6Q
作者:梦想de星空
相关推荐
- 国内外注塑机及电脑密码大全(常见注塑机通用密码)
-
一、国外注塑机(日本、德国等)东洋注塑机万能码:9422345日精注塑机密码:222|7777DAMEG注塑机密码:000000000新泻注塑机密码:241650|261450住友注塑机密码:...
- 并发编程实战来咯(并发编程的艺术和并发编程实战)
-
提到并发编程,就不得不提C++ConcurrencyinAction(SecondEdition)(《C++并发编程实战第2版》)啦!《C++并发编程实战第2版》英文原版&中文译版看到这个...
- 无锁队列Disruptor原理解析(无锁队列应用场景)
-
队列比较队列...
- 无锁CAS(附无锁队列的实现)(cas是一种无锁算法)
-
本文所有代码对应的Github链接为:https://github.com/dongyusheng/csdn-code/tree/master/cas_queue...
- Linux高性能服务器设计(linux 服务器性能)
-
C10K和C10M计算机领域的很多技术都是需求推动的,上世纪90年代,由于互联网的飞速发展,网络服务器无法支撑快速增长的用户规模。1999年,DanKegel提出了著名的C10问题:一台服务器上同时...
- 浅谈Go语言的并发控制(go语言 并发)
-
前言本文原创,著作权归...
- Datenlord |Etcd 客户端缓存实践(etcd 数据存储)
-
简介和背景...
- 无锁编程——从CPU缓存一致性讲到内存模型
-
缓存是一个非常常用的工程优化手段,其核心在于提升数据访问的效率。缓存思想基于局部性原理,这个原理包括时间局部性和空间局部性两部分:...
- 如何利用CAS技术实现无锁队列(cas会锁总线吗)
-
linux服务器开发相关视频解析:...
- Kotlin协程之一文看懂Channel管道
-
概述Channel类似于Java的BlockingQueue阻塞队列,不同之处在于Channel提供了挂起的send()和receive()方法。另外,通道Channel可以...
- 详解C++高性能无锁队列的原理与实现
-
1.无锁队列原理1.1.队列操作模型...
你 发表评论:
欢迎- 一周热门
-
-
前端面试:iframe 的优缺点? iframe有那些缺点
-
带斜线的表头制作好了,如何填充内容?这几种方法你更喜欢哪个?
-
漫学笔记之PHP.ini常用的配置信息
-
其实模版网站在开发工作中很重要,推荐几个参考站给大家
-
推荐7个模板代码和其他游戏源码下载的网址
-
[干货] JAVA - JVM - 2 内存两分 [干货]+java+-+jvm+-+2+内存两分吗
-
正在学习使用python搭建自动化测试框架?这个系统包你可能会用到
-
织梦(Dedecms)建站教程 织梦建站详细步骤
-
【开源分享】2024PHP在线客服系统源码(搭建教程+终身使用)
-
2024PHP在线客服系统源码+完全开源 带详细搭建教程
-
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)