别再手动赋值对象了,MapStruct了解一下!!!

引言

  • 你是否已经对手动get/set感到厌烦、恶心。什么???你就喜欢写这种代码,那这篇文章不适合你。

1.概述

  • 1.日常业务开发中我们经常会遇到以下几种对象:
    • PO(persistant object): 持久化对象,可以看成是与数据库中的表相映射的 java 对象。最简单的 PO 就是对应数据库中某个表中的一条记录。
    • VO(view object): 视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。
    • DTO(Data transport object):数据传输对象,它的作用主要是对外网络传输所使用。
    • 其他对象。
  • 2.那么对象属性的复制,通用的做法如下:
    • 操作1:(代码量蹭蹭就上去了,比如我):
    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>
    &lt;-- 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>
  • 2.自定义VO和DTO
@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;
 
    //省略get/set
}
  • 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.使用
    • 测试类
    // 定义VO
    CarVO carVO=CarVO.builder().id(1).numberOfSeats(123).build();
    // 实例化mapper(componentModel采用默认方式)
    CarDTO carDTO=CarMapper.INSTANCE.carVOToCarDTO(carVO);
    
    • 原理
      • 其实原理就是MapStruct插件会识别我们指定的接口(mapper),生成一个实现类,在实现类中,为我们实现了set逻辑。例如测试类的反编译结果如下图:
      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;
              }
          }
      }
      
  • 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 );
        
        // 若VO间存在同名字段,这需要指明source="xxVO.attribute"
        CarDTO twoCarVOToCarDTO(CarVO carVO,CarbVO carbVO);
    }
    
    • 5.枚举值映射,例如VO某个字段是enum类型,而DTO是String对象。
    // VO对象,省略@data
    public class CarVO{
        private Integer id;
        private BrandEnum brandEnum;
    }
    
    // DTO对象,省略@data
    public class CarDTO{
        private Integer id;
        private String brand;
    }
    
    // enum对象=
    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.参考
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值