引言
- 你是否已经对手动get/set感到厌烦、恶心。什么???你就喜欢写这种代码,那这篇文章不适合你。
1.概述
- 1.日常业务开发中我们经常会遇到以下几种对象:
- PO(persistant object): 持久化对象,可以看成是与数据库中的表相映射的 java 对象。最简单的 PO 就是对应数据库中某个表中的一条记录。
- VO(view object): 视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。
- DTO(Data transport object):数据传输对象,它的作用主要是对外网络传输所使用。
- 其他对象。
- 2.那么对象属性的复制,通用的做法如下:
StudentDO StudentDO = StudentDao.getById(id);
StudentVO studentVO = new StudentVO();
studentVO.setId(StudentDO.getId());
studentVO.setNickname(StudentDO.getName());
return studentVO;
- 操作2:BeanUtils.copyProperties(target,source)。 Ps:注意是Spring自带BenaUtils,引用Apache包下的BeanUtils存在很大性能问题(阿里代码插件扫描都不让你通过)。
BeanUtils.copertProperties(StudentVO,studentDO);
- 3.既然BeanUtils一行代码能搞定(你还要啥自行车?),对象间(简称A和B,下同)总在不断变化过程中,难免会遇到以下几种情况?
- A和B的字段名相同,值类型不同如何赋值?自己动手解决呗。
- B的字段名被人改了,A没法赋值了(说好的相守到老)?
- A和B某个集合类型(List等)字段进行赋值?
- …
- 通过上述3点我们可以发现,对象间的赋值主要在于如何定义映射规则(手动get/set、自动BeanUtils)。如果我们能自定义映射规则,同时又可以优雅的解决BeanUtils存在的特殊情况,代码会不会少很多呢(如下面示例)。MapStruct必须了解一下。
- 利用MapStruct对StudentVO的nickname和StudentDO的name赋值
@Mapping(source="name",target="nickname")
||
等价于
\/
studentVO.setNickname(studentDo.getName());
2.MapStruct
- MapStruct是一个代码生成器,它基于约定优先于配置的方法,极大地简化了javabean类型之间映射的实现。生成的映射代码使用纯方法调用,因此速度快、类型安全且易于理解。
3.接入指南
- 1.添加maven依赖(其他Gradle和Ant请参考官网),两个依赖包主要包含:
- 1.org.mapstruct:mapstrcut:包含必要注解(例如:@Mapper),若我们JDK版本高于1.8时,建议使用坐标mapstruct-jdk8,可以便于我们使用Java8的新特性。
- 2.org.mapstruct:mapstruct-processor:注解处理器,根据注解自动生成mapper的实现。
<dependency>
<groupId>org.mapstruct</groupId>
<-- jdk8以下就使用mapstruct -->
<artifactId>mapstruct-jdk8</artifactId>
<version>1.2.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.2.0.Final</version>
</dependency>
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CarVO {
private Integer id;
private Integer numberOfSeats;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CarDTO {
private Integer id;
private int seatCount;
}
- 3.自定义Mapper
- @Mapper注解的componentModel属性用户指定自动深层接口实现类的组件,具体如下:
类型 | 说明 |
---|
default | 默认情况,mapstruct不需要任何组件,可以通过Mappers.getMapper(Class)方式获取自动生成的实例对象 |
cdi | 上下文依赖注入,例如:@Injection |
spring | 生成的实现类上面会自动添加一个@Component注解,可以通过Spring的@Autowired方式进行注入 |
jsr330 | 生成的实现类上会添加@javax.inject.Named 和@Singleton注解,可以通过 @Inject注解获取 |
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDTO carVOToCarDTO(CarVO carVO);
}
- 4.使用
CarVO carVO=CarVO.builder().id(1).numberOfSeats(123).build();
CarDTO carDTO=CarMapper.INSTANCE.carVOToCarDTO(carVO);
- 5.优势
- 1.两个对象的字段名可不同(如4映射),通过使用@Mapping注解自定义字段映射。(多种映射关系可以使用@Mappings({@Mapping(xx),@Mapping(xx)}))
- 2.集合类型转换,比如我们要从List转换成List,只需要在Mapper接口中增加一个映射方法即可。
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDTO carVOToCarDTO(CarVO carVO);
List<CarDTO> convertToList(List<CarVO> carVOList);
}
public class CarMapperImpl implements CarMapper{
public CarMapperImpl(){
}
public CarDTO carVOToCarDTO(CarVO carVO){
if(carVO==null){
return null;
}else{
CarDTO carDTO=new CarDTO();
carDTO.setId(carVO.getId());
carDTO.setNumberOfSeats(carVO.getSeatCount());
return carDTO;
}
}
public List<CarDTO> convertToList(List<CarVO> carVOList){
if(carVOList==null){
return null;
}else{
List<CarDTO> list=new ArrayList(carVOList.size());
Iterator var3=carVOList.iterator();
while(var3.hasNext()){
CarVO carVO=var3.next();
list.add(this.carVOToCarDTO(carVO));
}
return list;
}
}
}
- 3.类型不一致(例如:CarDTO的createTime字段为String类型,CarVO的createTime字段为Date字段),我们可以通过自定义表达式expression="java(包路径)"来实现。
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
@Mappings({
@Mapping(source = "numberOfSeats", target = "seatCount"),
@Mapping(source = "createTime", expression = "java(DateTimeUtils.dateToStr(carVO.getCreateTime()))")
})
CarDTO carVOToCarDTO(CarVO carVO);
}
- 4.多对一。在实际业务开发中,存在多个VO转成一个DTO的情况,假设我们此时还有个CarbVO对象,我们需要将CarVO和CarbVO(两个VO分别有DTO的不同字段)组装成一个CarDTO,我们可以如下操作:
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
CarDTO twoCarVOToCarDTO(CarVO carVO,CarbVO carbVO);
}
- 5.枚举值映射,例如VO某个字段是enum类型,而DTO是String对象。
public class CarVO{
private Integer id;
private BrandEnum brandEnum;
}
public class CarDTO{
private Integer id;
private String brand;
}
public enum BrandEnum{
VOLVO("100","沃尔沃"),
BENZ("101","奔驰"),
BMW("102","宝马");
private int code;
private String name;
}
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
@Mapping(target="brand",source="brandEnum")
CarDTO convertToDTO(CarVO carVO);
}
public CarDTO convertToDTO(CarVO carVO){
if(carVO==null){
return null;
}else{
CarDTO carDTO=new CarDTO();
if(carVO.getBrandEnum()!=null){
carDTO.setBrand(carVO.getBrandEnum().name());
}
carDTO.setId(carVO.getId());
return carDTO;
}
}
- 3.小结
- 通过上述几个小demo可以看出MapStruct可以帮我们省去很多get/set操作,同时我们可以通过自定义相关规则来实现字段的映射、不同对象类型映射(String->int等)。当然我们也可以在expression中实现一些简单的计算逻辑,具体更多好玩的用法可以查考Guide.小结如下:
- 1.使用简单(通过@Mapper和@Mapping注解即可食用)。
- 2.支持自定义映射,针对传统BeanUtils无法拷贝不同类型、字段名,MapStruct支持自定义映射。
- 3.深拷贝及线程安全。通过上述反编译代码可知,MapStruct通过在代码编译时自动创建对象set赋值。
- 4.多对一。支持多个对象拷贝成一个对象。
- 4.参考