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 等,这里可能是我使用的还比较少,没发现什么比较好的方法