共计 13833 个字符,预计需要花费 35 分钟才能阅读完成。
平时做我的项目的时候,常常须要做 PO、VO、DTO 之间的转换。简略的对象转换,应用 BeanUtils 基本上是够了,然而简单的转换,如果应用它的话又得写一堆 Getter、Setter 办法了。明天给大家举荐一款对象主动映射工具
MapStruct
,性能真心弱小!
SpringBoot 实战电商我的项目 mall(50k+star)地址:https://github.com/macrozheng/mall
对于 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://mapstruct.org/documen…
我的项目源码地址
https://github.com/macrozheng…
本文 GitHub https://github.com/macrozheng/mall-learning 曾经收录,欢送大家 Star!