mapstruct 用法详细介绍

mapstruct 用法

mapstruct 是什么

mapstruct一款是用于生成类型安全的bean映射类的Java注解处理器 JSR 269规范,只需要定义一个接口,配置字段映射规则,使用注解处理器,在代码编译阶段,生成对应的映射实现类,使用的是纯get/set方法,无反射调用,并且在编译阶段可校验错误

官网:https://mapstruct.org/documentation/stable/reference/html/#_apache_maven

JSR 269规范:http://www.jcp.org/en/jsr/detail?id=269

用法

  • 使用maven配置,引入相关依赖
      <properties>
     	<!-- 定义版本-->
        <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
    </properties>
  <dependency>
            <groupId>org.mapstruct</groupId>
      		<!-- 相关注解包 @mapping-->
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <!--注解处理器-->
            <artifactId>mapstruct-processor</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>
  • 定义mapping接口, 一些常见mapping映射用法基本都在下面类中,可参考

    /**
     * <h3>Title: </h3>
     * <p>Description: </p>
     *
     * @author : lujia
     * @date : 2021-12-02
     **/
    @Mapper(componentModel = "spring")
    /**
     * @DecoratedWith 自定义转换器,需要手动实现所有的方法,
     * 使用 @Autowired 注入时默认使用的就是自定义转换器,
     * 需要原始转换器 请使用 @Qualifier("delegate")注入
     */
    @DecoratedWith(OrderMapperDecorator.class)
    public interface OrderMapper {
    
        @Mappings({
                //表达式转换
                @Mapping(target = "createDate", expression = "java(com.baibu.framework2.base.Times.toLocalDateTime(orderMapTestVo.getCreateDate()))"),
                // 字段名不一致
                @Mapping(target = "orderId", source = "orderMapTestVo.orderCode"),
                //嵌套
                @Mapping(target = "item.name", source = "orderMapTestVo.item.name"),
    
                // 多个参数
                @Mapping(target = "item.name1", source = "yesOrNoEnum.code"),
                // 使用自定义的方法转换参数 getPrice 就是 @Named 定义的method name
                @Mapping(target = "price", source = "orderMapTestVo.price", qualifiedByName = "getPrice")
                /**
                 * qualifiedBy 同上面 qualifiedByName 作用一样,只不过qualifiedBy 是通过注解定义的类来限定具体方法
                 * @Mapping(target = "price", source = "price", qualifiedBy = "getPrice")
                 */
        }
        )
        OrderMapTestDto voToDto(OrderMapTestVo orderMapTestVo, YesOrNoEnum yesOrNoEnum);
    
    
    //    @BeanMapping( resultType = OrderMapTestVo.class )
        // resultType 指定一个返回结果类型,在结果存在多个继承关系的时候可以用到
        //  qualifiedBy
        /**
         * 自定义一个字段转换方法
         *
         * @param price
         * @return
         */
        @Named("getPrice")
        default String getPrice(BigDecimal price) {
    
            return price.toString();
        }
    
        @Mappings({
                //表达式转换
                @Mapping(target = "createDate", expression = "java(com.baibu.framework2.base.Times.toLocalDateTime(orderMapTestVo.getCreateDate()))"),
                // 字段名不一致
                @Mapping(target = "orderId", source = "orderCode"),
                //嵌套
                @Mapping(target = "item", source = "item"),
    
                //@Mapping(target = "stringList",constant = "jack-jill-tom"),
                @Mapping(target = "str", constant = "aaaa"),
                // number 格式化 Number转String或String转Number才可以,如果是Number转Number则无效
                @Mapping(target = "price", source = "price", numberFormat = "$#.00")
                // 多个参数
        }
        )
        /**
         * NullValuePropertyMappingStrategy.IGNORE 忽略空值,
         * NullValuePropertyMappingStrategy.SET_TO_NULL 覆盖设置成null
         * NullValuePropertyMappingStrategy.SET_TO_DEFAULT 设置成默认值
         */
        @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
        OrderMapTestDto voToDto(OrderMapTestVo orderMapTestVo);
    
        // 列表
        List<OrderMapTestDto> voToDtos(List<OrderMapTestVo> orderMapTestVos);
    
        /**
         * stream
         *
         * @param orderMapTestVos
         * @return
         */
        List<OrderMapTestDto> voToDtos(Stream<OrderMapTestVo> orderMapTestVos);
    
        /**
         * 反向,字段名不一致还是需要单独配置mapping ,日期类型会自动转换成默认格式的string
         *
         * @param orderMapTestDto
         * @return
         */
        //指定日期格式化
        @Mapping(target = "createDate", dateFormat = "yyyy-MM-dd")
        OrderMapTestVo fromDtoToVo(OrderMapTestDto orderMapTestDto);
    
    
        @InheritInverseConfiguration(name = "voToDto")
        OrderMapTestVo inverseDtoToVo(OrderMapTestDto orderMapTestDto);
    }
    
    //---------------- dto
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class OrderMapTestDto {
    
        /**
         * 供应商订单主键id
         */
        private Long supplierOrderId;
    
        /**
         * 主键id
         */
        @NotNull(message = "主订单id 不可为空")
        private Long orderId;
    
        /**
         * 码单备注不可为空
         */
        @NotNull(message = "备注不可为空")
        private String poDetailRemark;
    
    
        private LocalDateTime createDate;
    
        private Item item;
    
        private String str;
    
        private List<Item> items;
    
    
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        public static class Item{
    
            private String name;
    
            private String name1;
    
    
            private String name2;
        }
    
    }
    
    //--------  vo
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class OrderMapTestVo {
    
        /**
         * 供应商订单主键id
         */
        private Long supplierOrderId;
    
        /**
         * 主键id
         */
        @NotNull(message = "主订单id 不可为空")
        private Long orderCode;
    
        /**
         * 码单备注不可为空
         */
        @NotNull(message = "备注不可为空")
        private String poDetailRemark;
    
    
        private String createDate;
    
    
        private Item item;
    
    
        private List<Item> items;
    
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        public static class Item{
    
            private String name;
        }
    
    }
    
  • 编译过后自动生成的mapping实现类

    @Generated(
        value = "org.mapstruct.ap.MappingProcessor",
        date = "2021-12-03T16:52:47+0800",
        comments = "version: 1.4.2.Final, compiler: javac, environment: Java 11.0.10 (AdoptOpenJDK)"
    )
    @Component
    @Qualifier("delegate")
    public class OrderMapperImpl_ implements OrderMapper {
    
        @Override
        public OrderMapTestDto voToDto(OrderMapTestVo orderMapTestVo, YesOrNoEnum yesOrNoEnum) {
            if ( orderMapTestVo == null && yesOrNoEnum == null ) {
                return null;
            }
    
            OrderMapTestDto orderMapTestDto = new OrderMapTestDto();
    
            if ( orderMapTestVo != null ) {
                if ( orderMapTestVo.getItem() != null ) {
                    if ( orderMapTestDto.getItem() == null ) {
                        orderMapTestDto.setItem( new Item() );
                    }
                    itemToItem( orderMapTestVo.getItem(), orderMapTestDto.getItem() );
                }
                orderMapTestDto.setOrderId( orderMapTestVo.getOrderCode() );
                orderMapTestDto.setPrice( getPrice( orderMapTestVo.getPrice() ) );
                orderMapTestDto.setSupplierOrderId( orderMapTestVo.getSupplierOrderId() );
                orderMapTestDto.setPoDetailRemark( orderMapTestVo.getPoDetailRemark() );
                orderMapTestDto.setItems( itemListToItemList( orderMapTestVo.getItems() ) );
            }
            if ( yesOrNoEnum != null ) {
                if ( orderMapTestDto.getItem() == null ) {
                    orderMapTestDto.setItem( new Item() );
                }
                yesOrNoEnumToItem( yesOrNoEnum, orderMapTestDto.getItem() );
            }
            orderMapTestDto.setCreateDate( com.baibu.framework2.base.Times.toLocalDateTime(orderMapTestVo.getCreateDate()) );
    
            return orderMapTestDto;
        }
    
        @Override
        public OrderMapTestDto voToDto(OrderMapTestVo orderMapTestVo) {
            if ( orderMapTestVo == null ) {
                return null;
            }
    
            OrderMapTestDto orderMapTestDto = new OrderMapTestDto();
    
            orderMapTestDto.setOrderId( orderMapTestVo.getOrderCode() );
            orderMapTestDto.setItem( itemToItem1( orderMapTestVo.getItem() ) );
            if ( orderMapTestVo.getPrice() != null ) {
                orderMapTestDto.setPrice( createDecimalFormat( "$#.00" ).format( orderMapTestVo.getPrice() ) );
            }
            orderMapTestDto.setSupplierOrderId( orderMapTestVo.getSupplierOrderId() );
            orderMapTestDto.setPoDetailRemark( orderMapTestVo.getPoDetailRemark() );
            orderMapTestDto.setItems( itemListToItemList( orderMapTestVo.getItems() ) );
    
            orderMapTestDto.setCreateDate( com.baibu.framework2.base.Times.toLocalDateTime(orderMapTestVo.getCreateDate()) );
            orderMapTestDto.setStr( "aaaa" );
    
            return orderMapTestDto;
        }
    
        @Override
        public List<OrderMapTestDto> voToDtos(List<OrderMapTestVo> orderMapTestVos) {
            if ( orderMapTestVos == null ) {
                return null;
            }
    
            List<OrderMapTestDto> list = new ArrayList<OrderMapTestDto>( orderMapTestVos.size() );
            for ( OrderMapTestVo orderMapTestVo : orderMapTestVos ) {
                list.add( voToDto( orderMapTestVo ) );
            }
    
            return list;
        }
    
        @Override
        public List<OrderMapTestDto> voToDtos(Stream<OrderMapTestVo> orderMapTestVos) {
            if ( orderMapTestVos == null ) {
                return null;
            }
    
            return orderMapTestVos.map( orderMapTestVo -> voToDto( orderMapTestVo ) )
            .collect( Collectors.toCollection( ArrayList<OrderMapTestDto>::new ) );
        }
    
        @Override
        public OrderMapTestVo fromDtoToVo(OrderMapTestDto orderMapTestDto) {
            if ( orderMapTestDto == null ) {
                return null;
            }
    
            OrderMapTestVo orderMapTestVo = new OrderMapTestVo();
    
            if ( orderMapTestDto.getCreateDate() != null ) {
                orderMapTestVo.setCreateDate( DateTimeFormatter.ofPattern( "yyyy-MM-dd" ).format( orderMapTestDto.getCreateDate() ) );
            }
            orderMapTestVo.setSupplierOrderId( orderMapTestDto.getSupplierOrderId() );
            if ( orderMapTestDto.getPrice() != null ) {
                orderMapTestVo.setPrice( new BigDecimal( orderMapTestDto.getPrice() ) );
            }
            orderMapTestVo.setPoDetailRemark( orderMapTestDto.getPoDetailRemark() );
            orderMapTestVo.setItem( itemToItem2( orderMapTestDto.getItem() ) );
            orderMapTestVo.setItems( itemListToItemList1( orderMapTestDto.getItems() ) );
    
            return orderMapTestVo;
        }
    
        @Override
        public OrderMapTestVo inverseDtoToVo(OrderMapTestDto orderMapTestDto) {
            if ( orderMapTestDto == null ) {
                return null;
            }
    
            OrderMapTestVo orderMapTestVo = new OrderMapTestVo();
    
            orderMapTestVo.setOrderCode( orderMapTestDto.getOrderId() );
            orderMapTestVo.setItem( itemToItem2( orderMapTestDto.getItem() ) );
            try {
                if ( orderMapTestDto.getPrice() != null ) {
                    orderMapTestVo.setPrice( (BigDecimal) createDecimalFormat( "$#.00" ).parse( orderMapTestDto.getPrice() ) );
                }
            }
            catch ( ParseException e ) {
                throw new RuntimeException( e );
            }
            orderMapTestVo.setSupplierOrderId( orderMapTestDto.getSupplierOrderId() );
            orderMapTestVo.setPoDetailRemark( orderMapTestDto.getPoDetailRemark() );
            if ( orderMapTestDto.getCreateDate() != null ) {
                orderMapTestVo.setCreateDate( DateTimeFormatter.ISO_LOCAL_DATE_TIME.format( orderMapTestDto.getCreateDate() ) );
            }
            orderMapTestVo.setItems( itemListToItemList1( orderMapTestDto.getItems() ) );
    
            return orderMapTestVo;
        }
    
        private DecimalFormat createDecimalFormat( String numberFormat ) {
    
            DecimalFormat df = new DecimalFormat( numberFormat );
            df.setParseBigDecimal( true );
            return df;
        }
    
        protected void itemToItem(com.baibu.order.application.vo.req.OrderMapTestVo.Item item, Item mappingTarget) {
            if ( item == null ) {
                return;
            }
    
            mappingTarget.setName( item.getName() );
        }
    
        protected void yesOrNoEnumToItem(YesOrNoEnum yesOrNoEnum, Item mappingTarget) {
            if ( yesOrNoEnum == null ) {
                return;
            }
    
            mappingTarget.setName1( yesOrNoEnum.getCode() );
        }
    
        protected Item itemToItem1(com.baibu.order.application.vo.req.OrderMapTestVo.Item item) {
            if ( item == null ) {
                return null;
            }
    
            Item item1 = new Item();
    
            item1.setName( item.getName() );
    
            return item1;
        }
    
        protected List<Item> itemListToItemList(List<com.baibu.order.application.vo.req.OrderMapTestVo.Item> list) {
            if ( list == null ) {
                return null;
            }
    
            List<Item> list1 = new ArrayList<Item>( list.size() );
            for ( com.baibu.order.application.vo.req.OrderMapTestVo.Item item : list ) {
                list1.add( itemToItem1( item ) );
            }
    
            return list1;
        }
    
        protected com.baibu.order.application.vo.req.OrderMapTestVo.Item itemToItem2(Item item) {
            if ( item == null ) {
                return null;
            }
    
            com.baibu.order.application.vo.req.OrderMapTestVo.Item item1 = new com.baibu.order.application.vo.req.OrderMapTestVo.Item();
    
            item1.setName( item.getName() );
    
            return item1;
        }
    
        protected List<com.baibu.order.application.vo.req.OrderMapTestVo.Item> itemListToItemList1(List<Item> list) {
            if ( list == null ) {
                return null;
            }
    
            List<com.baibu.order.application.vo.req.OrderMapTestVo.Item> list1 = new ArrayList<com.baibu.order.application.vo.req.OrderMapTestVo.Item>( list.size() );
            for ( Item item : list ) {
                list1.add( itemToItem2( item ) );
            }
    
            return list1;
        }
    }
    
    
  • 自定义装饰者

    /**
     * <h3>Title: </h3>
     * <p>Description: </p>
     *
     * @author : lujia
     * @date : 2021-12-02
     * 这里其实算是一个装饰器的模式,OrderMapperDecorator 包装了原来的OrderMapper ,
     * 提供额外的能力,然后再委托原来的OrderMapper的处理
     **/
    @Slf4j
    @RequiredArgsConstructor
    public abstract class OrderMapperDecorator implements OrderMapper {
    
        @Autowired
        @Qualifier("delegate")
        private  OrderMapper delegate;
    
        @Override
        public OrderMapTestDto voToDto(OrderMapTestVo orderMapTestVo, YesOrNoEnum yesOrNoEnum) {
            log.info("自定义转换器");
            return delegate.voToDto(orderMapTestVo, yesOrNoEnum);
    
        }
    
        @Override
        public OrderMapTestDto voToDto(OrderMapTestVo orderMapTestVo) {
            return delegate.voToDto(orderMapTestVo);
        }
    
        @Override
        public List<OrderMapTestDto> voToDtos(List<OrderMapTestVo> orderMapTestVos) {
            return delegate.voToDtos(orderMapTestVos);
        }
    
        @Override
        public List<OrderMapTestDto> voToDtos(Stream<OrderMapTestVo> orderMapTestVos) {
            return delegate.voToDtos(orderMapTestVos);
        }
    
        @Override
        public OrderMapTestVo fromDtoToVo(OrderMapTestDto orderMapTestDto) {
            return null;
        }
    }
    
    
  • 编译过后自定义装饰类实现

    @Generated(
        value = "org.mapstruct.ap.MappingProcessor",
        date = "2021-12-03T16:52:47+0800",
        comments = "version: 1.4.2.Final, compiler: javac, environment: Java 11.0.10 (AdoptOpenJDK)"
    )
    @Component
    @Primary
    public class OrderMapperImpl extends OrderMapperDecorator implements OrderMapper {
    
        @Autowired
        @Qualifier("delegate")
        private OrderMapper delegate;
    
        @Override
        public OrderMapTestVo inverseDtoToVo(OrderMapTestDto orderMapTestDto)  {
            return delegate.inverseDtoToVo( orderMapTestDto );
        }
    }
    
  • 使用

      	 /**
         * 如果使用@DecoratedWith(OrderMapperDecorator.class)自定义了 转换器,此处默认注入的就是自定义的OrderMapperDecorator ,
         * 如果需要注入OrderMapper 请使用 @Qualifier("delegate")注解注入
         */
     	@Autowired
        private OrderMapper orderMapper;
    
        @PostMapping("/testMapstruct")
        public OrderMapTestDto testMapstruct(@RequestBody OrderMapTestVo orderMapTestVo) {
    
            OrderMapTestDto orderMapTestDto = orderMapper.voToDto(orderMapTestVo, YesOrNoEnum.YES);
    
            OrderMapTestVo orderMapTestVo1 = new OrderMapTestVo();
            BeanUtils.copyProperties(orderMapTestVo,orderMapTestVo1);
    
            List<OrderMapTestVo> list=new ArrayList<>();
            list.add(orderMapTestVo);
            list.add(orderMapTestVo1);
            List<OrderMapTestDto> orderMapTestDtos = orderMapper.voToDtos(list);
    
            log.info(Json.serialize(orderMapTestDtos));
    
            OrderMapTestVo orderMapTestVo2 = orderMapper.fromDtoToVo(orderMapTestDto);
            log.info(Json.serialize(orderMapTestVo2));
            OrderMapTestVo orderMapTestVo3 = orderMapper.inverseDtoToVo(orderMapTestDto);
            log.info(Json.serialize(orderMapTestVo3));
    
            return orderMapTestDto;
        }
    

总结

优点

  • 相比BeanUtils的反射,使用mapstruct性能肯定更好,相比手动set也减轻一丢丢工作量
  • mapstruct 在编译阶段就会校验一些错误,列如字段不存在,方法不存在
  • 包装类型自动转换,自动格式化

缺点

  • 当字段名称,类型不一致时,同样需要定义一堆映射关系,比较繁琐,同时这种映射只会在编译阶段校验,发现
  • 多了很多class文件,如果使用spring注入方式,也会多了一堆 spring bean 定义,占用jvm内存空间
  • 复杂映射支持不足,比如多重嵌套list ,map 等,这里可能是我使用的还比较少,没发现什么比较好的方法
上一篇:MapStruct填坑 -- 重新生成实现类(转)


下一篇:【云吞铺子之专家来了】linux系统启停问题之如何解决因为分区丢失导致的系统启动失败问题