1 MapStruct配置
MapStuct的使用非常简单,把对应的jar包引入即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 | <properties> <mapstruct.version> 1.3 . 1 .Final</mapstruct.version> </properties> <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> </dependency> |
2 原理&性能
2.1 实现原理
对象拷贝工具实现上一般分为2种:
(1) 在运行时,通过反射调用set/get方法或者直接对成员变量进行赋值。
(2)在编译期,生成调用get/set方法进行赋值的代码,生成对应的class文件。
MapStrut属于第二种,在编译期间消耗少许的时间,换取运行时的高性能。
接口声明:
1 2 3 4 | @Mapper public interface ProductAssembler { SkuDTO toDTO(Sku sku); } |
编辑生成的class反编译
1 2 3 4 5 6 7 8 9 10 11 | public class ProductAssemblerImpl implements ProductAssembler { @Override public SkuDTO toDTO(Sku sku) { if ( sku == null ) { return null ; } SkuDTO skuDTO = new SkuDTO(); skuDTO.setSkuId( sku.getSkuId() ); return skuDTO; } } |
3 使用方法
使用@Mapper注解,声明映射器,可以是接口,或者抽象类。
使用@Mapping注解,实现灵活的字段映射,定制映射的规则。
3.1 转换器的检索
在声明好转换接口之后,MapStruct提供几种方式获取生成的Mapper映射器。
3.1.1 使用Mappers工厂获取
可以通过提供的Mappers工厂类,获取指定的类型。
1 2 3 4 5 6 | @Mapper public interface Assembler { //使用工厂方法获取Mapper实例 Assembler INSTANCE = Mappers.getMapper(Assembler. class ); ProductDTO toDTO(Product product); } |
3.1.2 通过依赖注入的方式获取
MapStuct同时支持和其他框架结合,通过依赖注入的方式获取Mapper实例。目前支持spring和cdi。
1 2 3 4 | @Mapper (componentModel = "spring" ) public interface Assembler { ProductDTO toDTO(Product product); } |
1 2 3 4 5 6 7 8 9 10 11 12 | @Component public class AssemblerImpl implements Assembler { @Override public ProductDTO toDTO(Product product) { if ( product == null ) { return null ; } ProductDTO productDTO = new ProductDTO(); productDTO.setProductId( product.getProductId() ); return productDTO; } } |
3.2 简单映射
3.2.1 基本映射
对于同名同属性的字段,无需特别声明指定,自动转换。
对于不同名相同属性的字段,可以使用Mapping注解指定。
1 2 3 4 5 6 7 | @Data @NoArgsConstructor @AllArgsConstructor public class Product { private String productId; private String name; } |
1 2 3 4 5 6 7 8 | @Data @NoArgsConstructor @AllArgsConstructor public class ProductDTO implements Serializable { private static final long serialVersionUID = -6780322740093464581L; private String productId; private String productName; } |
定义映射器:
1 2 3 4 5 | @Mapper (componentModel = "spring" ) public interface Assembler { @Mapping (source = "name" , target = "productName" ) ProductDTO toDTO(Product product); } |
生成的映射器试实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Component public class AssemblerImpl implements Assembler { @Override public ProductDTO toDTO(Product product) { if ( product == null ) { return null ; } ProductDTO productDTO = new ProductDTO(); productDTO.setProductName( product.getName() ); //不同字段名映射 productDTO.setProductId( product.getProductId() ); //相同映射名自动转换 return productDTO; } } |
3.2.2 多源参数映射
支持把多个参数映射成一个类型,使用@Mapping指定即可。
1 2 3 4 5 6 | @Mapper (componentModel = "spring" ) public interface Demo6Assembler { @Mapping (target = "productId" , source = "product.productId" ) @Mapping (target = "desc" , source = "detail.desc" ) ProductDTO toDetailDTO(Product product, ProductDetail detail); } |
3.2.3 更新对象
映射时除了生成新的新对象外,还支持现存对象的更新:
1 2 3 4 5 | @Mapper (componentModel = "spring" ) public interface Demo6Assembler { @Mapping (target = "desc" , source = "desc" ) void updateDTO( @MappingTarget ProductDTO productDTO, ProductDetail detail); } |
3.3 数据类型转换
3.3.1 对于基础数据类型会进行自动隐式的转换
如int、long、String,Integer、Long等。
1 2 3 4 5 6 7 | @Data @NoArgsConstructor @AllArgsConstructor public class Product { private String productId; private Long price; } |
1 2 3 4 5 6 7 8 | @Data @NoArgsConstructor @AllArgsConstructor public class ProductDTO implements Serializable { private static final long serialVersionUID = -6780322740093464581L; private int productId; private String price; } |
定义映射器:
1 2 3 4 | @Mapper (componentModel = "spring" ) public interface Assembler { ProductDTO toDTO(Product product); } |
生成的映射代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @Component public class AssemblerImpl implements Assembler { @Override public ProductDTO toDTO(Product product) { if ( product == null ) { return null ; } ProductDTO productDTO = new ProductDTO(); if ( product.getProductId() != null ) { //String自动转int productDTO.setProductId( Integer.parseInt( product.getProductId() ) ); } if ( product.getPrice() != null ) { //Long转String productDTO.setPrice( String.valueOf( product.getPrice() ) ); } return productDTO; } } |
3.3.2 指定转换格式
某些类型的转换,我们可以指定具体转换的格式。
(1)对于基本数据类型与String之间的转换,可以使用numberFormat 指定转换格式,使用的是java.text.DecimalFormat 实现。
1 2 3 4 5 6 7 8 | @Data @NoArgsConstructor @AllArgsConstructor public class Product { private String productId; private BigDecimal price; private String stock; } |
1 2 3 4 5 6 7 8 9 | @Data @NoArgsConstructor @AllArgsConstructor public class ProductDTO implements Serializable { private static final long serialVersionUID = -6780322740093464581L; private String productId; private String price; private Integer stock; } |
映射器定义:
1 2 3 4 5 6 | @Mapper (componentModel = "spring" ) public interface Demo3Assembler { @Mapping (target = "price" , numberFormat = "#.00元" ) //BigDecimal转换成字符串 @Mapping (target = "stock" , numberFormat = "#个" ) //字符串转换成int ProductDTO toDTO(Product product); } |
实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | @Component public class Demo3AssemblerImpl implements Demo3Assembler { @Override public ProductDTO toDTO(Product product) { if ( product == null ) { return null ; } ProductDTO productDTO = new ProductDTO(); productDTO.setProductId( product.getProductId() ); if ( product.getPrice() != null ) { //BigDecimal格式化成字符串 productDTO.setPrice( createDecimalFormat( "#.00元" ).format( product.getPrice() ) ); } try { if ( product.getStock() != null ) { //字符串格式化为int productDTO.setStock( new DecimalFormat( "#个" ).parse( product.getStock() ).intValue() ); } } catch ( ParseException e ) { throw new RuntimeException( e ); } return productDTO; } private DecimalFormat createDecimalFormat( String numberFormat ) { DecimalFormat df = new DecimalFormat( numberFormat ); df.setParseBigDecimal( true ); return df; } } |
测试代码:
1 2 3 4 5 6 7 8 9 10 | @Test public void test2() { com.gotten.study.mapstruct.demo3.Product product = new com.gotten.study.mapstruct.demo3.Product (); product.setProductId( "P001" ); product.setPrice( new BigDecimal( "100" )); product.setStock( "1个" ); com.gotten.study.mapstruct.demo3.ProductDTO productDTO = demo3Assembler.toDTO(product); System.out.println( "productDTO:" + JSON.toJSONString(productDTO)); } productDTO:{ "price" : "100.00元" , "productId" : "P001" , "stock" : 1 } |
(2)Date和String之间的转换,可以通过dateFormat指定转换格式,使用的是SimpleDateFormat的实现。
1 2 3 4 5 6 7 8 | @Data @NoArgsConstructor @AllArgsConstructor public class Product { private String productId; private Date saleTime; private String validTime; } |
1 2 3 4 5 6 7 8 9 | @Data @NoArgsConstructor @AllArgsConstructor public class ProductDTO implements Serializable { private static final long serialVersionUID = -6780322740093464581L; private String productId; private String saleTime; private Date validTime; } |
定义映射器:
1 2 3 4 5 6 | @Mapper (componentModel = "spring" ) public interface Demo4Assembler { @Mapping (target = "saleTime" , dateFormat = "yyyy-MM-dd HH:mm:ss" ) //Date转换成String @Mapping (target = "validTime" , dateFormat = "yyyy-MM-dd HH:mm" ) //String转换成Date ProductDTO toDTO(Product product); } |
实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @Component public class Demo4AssemblerImpl implements Demo4Assembler { @Override public ProductDTO toDTO(Product product) { if ( product == null ) { return null ; } ProductDTO productDTO = new ProductDTO(); productDTO.setProductId( product.getProductId() ); if ( product.getSaleTime() != null ) { productDTO.setSaleTime( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ).format( product.getSaleTime() ) ); //转换成String } try { if ( product.getValidTime() != null ) { productDTO.setValidTime( new SimpleDateFormat( "yyyy-MM-dd HH:mm" ).parse( product.getValidTime() ) ); //转换成Date } } catch ( ParseException e ) { throw new RuntimeException( e ); } return productDTO; } } |
3.3.3 属性为复杂对象的映射
(1)如果是相同类型的对象引用,不会创建新的对象,直接把对象的引用从源对象赋值给目标对象。
(2)如果类型相同,但是是集合类的引用,会创建一个新的集合,集合里面的所有引用进行拷贝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Override public ProductDTO toDTO(Product product) { if ( product == null ) { return null ; } ProductDTO productDTO = new ProductDTO(); productDTO.setProductId( product.getProductId() ); List<Sku> list = product.getSkuList(); if ( list != null ) { productDTO.setSkuList( new ArrayList<Sku>( list ) ); //创建新的集合,并对所有元素进行拷贝 } return productDTO; } |
(3)对象的类型不同,会检查映射器中是否存在对应的映射方法,如果存在,直接使用,否则会尝试自动创建子映射方法。
1 2 3 4 5 6 7 | @Data @AllArgsConstructor @NoArgsConstructor public class Product { private String productId; private ProductDetail productDetail; } |
1 2 3 4 5 6 | @Data @AllArgsConstructor @NoArgsConstructor public class ProductDetail { private String id; } |
1 2 3 4 5 6 7 8 | @Data @AllArgsConstructor @NoArgsConstructor public class ProductDTO implements Serializable { private static final long serialVersionUID = 2184784038009791692L; private String productId; private ProductDetailDTO productDetail; } |
1 2 3 4 5 6 | @Data @AllArgsConstructor @NoArgsConstructor public class ProductDetailDTO { private String detailId; } |
定义映射器:
1 2 3 4 5 6 7 | @Mapper (componentModel = "spring" ) public interface Demo6Assembler { ProductDTO toDTO(Product product); @Mapping (target = "detailId" , source = "id" ) ProductDetailDTO toDetailDTO(ProductDetail detail); } |
生成代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @Component public class Demo6AssemblerImpl implements Demo6Assembler { @Override public ProductDTO toDTO(Product product) { if ( product == null ) { return null ; } ProductDTO productDTO = new ProductDTO(); productDTO.setProductId( product.getProductId() ); productDTO.setProductDetail( toDetailDTO( product.getProductDetail() ) ); //查找使用存在的转换方法 return productDTO; } public ProductDetailDTO toDetailDTO(ProductDetail detail) { if ( detail == null ) { ProductDetailDTO productDetailDTO = new ProductDetailDTO(); productDetailDTO.setDetailId( detail.getId() ); return productDetailDTO; } |
(4)多层bean之间的转换
@Mapping注解支持跨层级的属性转换,属性可以在不同层级之间切换。
1 2 3 4 5 6 7 | @Data @AllArgsConstructor @NoArgsConstructor public class Product { private String productId; private ProductDetail productDetail; } |
1 2 3 4 5 6 | @Data @AllArgsConstructor @NoArgsConstructor public class ProductDetail { private String id; } |
1 2 3 4 5 6 7 8 | @Data @AllArgsConstructor @NoArgsConstructor public class ProductDTO implements Serializable { private static final long serialVersionUID = 2184784038009791692L; private String productId; private ProductDetailDTO productDetail; } |
1 2 3 4 5 6 7 | @Data @AllArgsConstructor @NoArgsConstructor public class ProductDetailDTO { private String productId; private String detailId; } |
定义映射器:
1 2 3 4 5 6 | @Mapper (componentModel = "spring" ) public interface Demo7Assembler { @Mapping (target = "productDetail.detailId" , source = "productDetail.id" ) //声明productDetail下的属性转换规则 @Mapping (target = "productDetail.productId" , source = "productId" ) //跨层级的属性转换,把product层级的productId放到productDetail层级 ProductDTO toDTO(Product product); } |
生成代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | @Component public class Demo7AssemblerImpl implements Demo7Assembler { @Override public ProductDTO toDTO(Product product) { if ( product == null ) { return null ; } ProductDTO productDTO = new ProductDTO(); if ( product.getProductDetail() != null ) { if ( productDTO.getProductDetail() == null ) { productDTO.setProductDetail( new ProductDetailDTO() ); } productDetailToProductDetailDTO( product.getProductDetail(), productDTO.getProductDetail() ); if ( productDTO.getProductDetail() == null ) { productDTO.setProductDetail( new ProductDetailDTO() ); productToProductDetailDTO( product, productDTO.getProductDetail() ); productDTO.setProductId( product.getProductId() ); return productDTO; } //detail的转换方法 protected void productDetailToProductDetailDTO(ProductDetail productDetail, ProductDetailDTO mappingTarget) { if ( productDetail == null ) { return ; mappingTarget.setDetailId( productDetail.getId() ); //product转成detail(更新处理) protected void productToProductDetailDTO(Product product, ProductDetailDTO mappingTarget) { mappingTarget.setProductId( product.getProductId() ); } |
3.3.4 自定义转换器
MapStruct支持自定义转换器,实现类型之间的转换自定义的规则。
一个自定义映射器可以定义多个映射方法,匹配时,是以方法的入参和出参进行匹配的。如果绑定的映射中,存在多个相同的入参和出参方法,将会报错。
如果多个入参或者出参方法存在继承关系,将会匹配最具体的那一个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Data @AllArgsConstructor @NoArgsConstructor public class Product { private String productId; private List<String> images; } @Data @AllArgsConstructor @NoArgsConstructor public class ProductDTO implements Serializable { private static final long serialVersionUID = 2184784038009791692L; private String productId; private String images; } |
定义映射器:
1 2 3 4 5 6 | @Component public class ImageFormater { public String format(List<String> images) { return String.join( "," , images); } } |
绑定转换器:
1 2 3 4 | @Mapper (componentModel = "spring" , uses = ImageFormater. class ) public interface Demo8Assembler { ProductDTO toDTO(Product product); } |
映射器实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Component public class Demo8AssemblerImpl implements Demo8Assembler { @Autowired private ImageFormater imageFormater; @Override public ProductDTO toDTO(Product product) { if ( product == null ) { return null; } ProductDTO productDTO = new ProductDTO(); productDTO.setProductId( product.getProductId() ); //调用自定义的映射器进行映射,把list转成string productDTO.setImages( imageFormater.format( product.getImages() ) ); return productDTO; } } |
3.3.5 使用限定符限定使用转换方法
自定义转换器时,存在多个相同入参和出参的方法,MapStruct无法匹配使用哪个映射方法。这时可以使用限定符绑定每个属性转换时使用的转换方法。
(1)限定符使用自定义注解实现。
声明限定符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import org.mapstruct.Qualifier; //映射器上的限定符 @Qualifier //标记为限定符 @Target (ElementType.TYPE) @Retention (RetentionPolicy.CLASS) public @interface Formators { } //映射方法上的限定符 @Qualifier //标记为限定符 @Target (ElementType.METHOD) @Retention (RetentionPolicy.CLASS) public @interface FormatImages { } //映射方法上的限定符 @Qualifier //标记为限定符 @Target (ElementType.METHOD) @Retention (RetentionPolicy.CLASS) public @interface FormatDetails { } |
绑定限定符到映射器的方法上面:
1 2 3 4 5 6 7 8 9 10 11 12 | @Component @Formators //绑定限定符 public class CusFormater { @FormatImages //绑定限定符 public String formatImages(List<String> images) { return String.join( "," , images); } @FormatDetails //绑定限定符 public String formatDetails(List<String> images) { return String.join( "," , images); } } |
映射时,绑定限定符,定位映射方法:
1 2 3 4 5 6 | @Mapper (componentModel = "spring" , uses = CusFormater. class ) public interface Demo9Assembler { @Mapping (target = "images" , qualifiedBy = FormatImages. class ) //转换指定限定符,定位具体的映射方法 @Mapping (target = "details" , qualifiedBy = FormatDetails. class ) //转换指定限定符,定位具体的映射方法 ProductDTO toDTO(Product product); } |
生成代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Component public class Demo9AssemblerImpl implements Demo9Assembler { @Autowired private CusFormater cusFormater; @Override public ProductDTO toDTO(Product product) { if ( product == null ) { return null ; } ProductDTO productDTO = new ProductDTO(); productDTO.setProductId( product.getProductId() ); productDTO.setImages( cusFormater.formatImages( product.getImages() ) ); //定位方法 productDTO.setDetails( cusFormater.formatDetails( product.getDetails() ) ); return productDTO; } } |
(2)基于named注解实现(推荐)
除了使用自定义注解的方法,还可以使用@Named注解实现限定符的绑定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Component @Named ( "CusFormater" ) public class CusFormater { //绑定限定符 @Named ( "formatImages" ) public String formatImages(List<String> images) { return String.join( "," , images); } //绑定限定符 @Named ( "formatDetails" ) public String formatDetails(List<String> images) { return String.join( "," , images); } } |
使用时绑定:
1 2 3 4 5 6 | @Mapper (componentModel = "spring" , uses = CusFormater. class ) public interface Demo10Assembler { @Mapping (target = "images" , qualifiedByName = "formatImages" ) //转换指定限定符,定位具体的映射方法 @Mapping (target = "details" , qualifiedByName = "formatDetails" ) //转换指定限定符,定位具体的映射方法 ProductDTO toDTO(Product product); } |
3.4 Map的映射
可以使用@MapMapping实现对key和value的分别映射:
1 2 3 4 5 | @Mapper (componentModel = "spring" ) public interface Demo11Assembler { @MapMapping (valueDateFormat = "yyyy-MM-dd HH:mm:ss" ) Map<String, String> toDTO(Map<Long, Date> map); } |
3.5 枚举值之间的转换
MapStruct可以在多个枚举值之间转换,使用@ValueMapping注解。
1 2 3 4 5 6 7 8 9 10 11 12 13 | public enum E1 { E1_1, E1_2, E1_3, ; } public enum E2 { E2_1, E2_2, E2_3, ; } |
1 2 3 4 5 6 7 | @Mapper (componentModel = "spring" ) public interface Demo11Assembler { @ValueMapping (target = "E1_1" , source = "E2_1" ) @ValueMapping (target = "E1_2" , source = "E2_2" ) @ValueMapping (target = MappingConstants.NULL, source = "E2_3" ) //转换成null E1 toDTO(E2 e2); } |
生成代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @Component public class Demo11AssemblerImpl implements Demo11Assembler { @Override public E1 toDTO(E2 e2) { if ( e2 == null ) { return null ; } E1 e1; switch ( e2 ) { case E2_1: e1 = E1.E1_1; break ; case E2_2: e1 = E1.E1_2; break ; case E2_3: e1 = null ; break ; default : throw new IllegalArgumentException( "Unexpected enum constant: " + e2 ); } return e1; } } |
3.6 定制Bean生成
使用MapStruct可以使用对象工厂来创建bean,同时也可以更新bean。
定义对象工厂:
1 2 3 4 5 6 7 | public class DTOFactory { public ProductDTO createDTO() { ProductDTO productDTO = new ProductDTO(); productDTO.setStock( 0 ); return productDTO; } } |
使用对象工厂:
1 2 3 4 | @Mapper (componentModel = "spring" , uses = DTOFactory. class ) //指定使用的对象工厂 public interface Demo13Assembler { ProductDTO toDTO(Product product); } |
生成代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @Component public class Demo13AssemblerImpl implements Demo13Assembler { @Autowired private DTOFactory dTOFactory; @Override public ProductDTO toDTO(Product product) { if ( product == null ) { return null ; } ProductDTO productDTO = dTOFactory.createDTO(); //使用对象工厂创建对象 productDTO.setProductId( product.getProductId() ); return productDTO; } } |
3.7 缺省值和常量
MapStruct允许设置缺省值和常量,同时缺省值允许使用表达式。
注意:使用缺省值,源字段必须存在,否则缺省值不生效,否则应该使用常量。
1 2 3 4 5 6 7 8 | @Mapper (componentModel = "spring" , imports = UUID. class ) public interface Demo15Assembler { @Mapping (target = "productId" , source = "productId" , defaultValue = "0" ) //当product的productId为null,设置为0 @Mapping (target = "random" , source = "random" , defaultExpression = "java(UUID.randomUUID().toString())" ) //缺省设置随机数 @Mapping (target = "stock" , constant = "0" ) //固定设置为0 @Mapping (target = "createTime" , dateFormat = "yyyy-MM-dd" , constant = "2020-05-30" ) //固定格式化设置为2020-05-30, ProductDTO toDTO(Product product); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | @Component public class Demo15AssemblerImpl implements Demo15Assembler { @Override public ProductDTO toDTO(Product product) { if ( product == null ) { return null ; } ProductDTO productDTO = new ProductDTO(); if ( product.getProductId() != null ) { productDTO.setRandom( product.getProductId() ); } else { productDTO.setRandom( UUID.randomUUID().toString() ); } if ( product.getProductId() != null ) { productDTO.setProductId( product.getProductId() ); } else { productDTO.setProductId( "0" ); } productDTO.setStock( 0 ); try { productDTO.setCreateTime( new SimpleDateFormat( "yyyy-MM-dd" ).parse( "2020-05-30" ) ); } catch ( ParseException e ) { throw new RuntimeException( e ); } return productDTO; } } |
3.8 存在继承关系的结果处理
当返回的结果类型存在继承关系时,可以使用@BeanMapping注解指定真实返回的结果类型。
1 2 3 4 5 | @Mapper (componentModel = "spring" ) public interface Demo17Assembler { @BeanMapping (resultType = DogDTO. class ) //指定返回的结果类型 Animal toDTO(Dog dog); } |
1 2 3 4 5 6 7 8 9 10 11 12 | @Component public class Demo17AssemblerImpl implements Demo17Assembler { @Override public Animal toDTO(Dog dog) { if ( dog == null ) { return null ; } DogDTO animal = new DogDTO(); animal.setId( dog.getId() ); return animal; } } |
3.9 映射关系继承
MapStruct允许对映射关系进行继承,使用@InheritConfiguration标记当前方法继承其他映射方法的映射关系。会自动查找相同类型映射源、映射目标的方法进行继承,如果存在多个相同类型的方法,则需要手工指定。
1 2 3 4 5 6 7 8 9 10 11 12 | @Mapper (componentModel = "spring" ) public interface Demo18Assembler { @Mapping (target = "productId" , source = "id" ) @Mapping (target = "detail" , source = "detail1" ) ProductDTO toDTO(Product product); @Mapping (target = "productId" , source = "id2" ) @Mapping (target = "detail" , source = "detail2" ) ProductDTO toDTO2(Product product); @InheritConfiguration (name = "toDTO" ) //对toDTO的映射关系进行继承 @Mapping (target = "detail" , source = "detail2" ) //对继承的关系进行重写 void update( @MappingTarget ProductDTO productDTO, Product product); } |
除了正向继承规则外,还可以进行规则逆向继承,从被继承方法的目标对象映射到源对象。
1 2 3 4 5 6 7 8 9 10 11 12 | @Mapper (componentModel = "spring" ) public interface Demo18Assembler { @Mapping (target = "productId" , source = "id" ) @Mapping (target = "detail" , source = "detail1" ) ProductDTO toDTO(Product product); @Mapping (target = "productId" , source = "id2" ) @Mapping (target = "detail" , source = "detail2" ) ProductDTO toDTO2(Product product); @InheritInverseConfiguration (name = "toDTO" ) //对toDTO的映射关系进行逆继承 @Mapping (target = "detail2" , source = "detail" ) //对逆向继承的关系进行重写 Product toEntity(ProductDTO dto); } |
3.10 复杂映射的实现
有时候我们除了普通映射外,还需要进行一些复杂的映射,如把多个字段计算映射成一个字段,或者借用一些工具进行映射的计算等。MapStruct提供了集中方式实现。
3.10.1 使用java表达式进行映射
对于复杂的映射,允许使用java表达式实现字段的映射。
注意要导入使用到的类。
1 2 3 4 5 6 | @Mapper (componentModel = "spring" , imports = DecimalUtils. class ) //导入java表达式使用的类 public interface Demo16Assembler { @Mapping (target = "price" , expression = "java(product.getPrice1() + product.getPrice2())" ) //直接相加 @Mapping (target = "price2" , expression = "java(DecimalUtils.add(product.getPrice1(), product.getPrice2()))" ) //使用工具类处理 ProductDTO toDTO(Product product); } |
生成的映射代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @Component public class Demo16AssemblerImpl implements Demo16Assembler { @Override public ProductDTO toDTO(Product product) { if ( product == null ) { return null ; } ProductDTO productDTO = new ProductDTO(); productDTO.setProductId( product.getProductId() ); productDTO.setPrice( product.getPrice1() + product.getPrice2() ); productDTO.setPrice2( DecimalUtils.add(product.getPrice1(), product.getPrice2()) ); return productDTO; } } |
3.10.2 使用装饰器进行映射
MapStruct允许使用装饰器进行一些复杂映射,同时可以支持和Spring结合。
定义一个映射器,同时声明绑定装饰器:
1 2 3 4 5 | @Mapper (componentModel = "spring" ) @DecoratedWith (Demo18AssemblerDecorator. class ) //声明绑定装饰器 public interface Demo18Assembler { ProductDTO toDTO(Product product); } |
定义装饰器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public abstract class Demo18AssemblerDecorator implements Demo18Assembler { @Autowired @Qualifier ( "delegate" ) //注入mapStruct生成的转换器,原始的转换器注入spring时,会使用delegate装饰符 private Demo18Assembler assembler; //可以获取spring的bean进行操作 @Autowired private StringUtils stringUtils; @Override public ProductDTO toDTO(Product product) { //调用MapStruct进行转换 ProductDTO productDTO = assembler.toDTO(product); //自定义操作 stringUtils.join(product.getName(), "-" , product.getTitle()); return productDTO; } } |
生成装饰器代码:
1 2 3 4 | @Component @Primary //Primary修饰,方便使用时直接使用autowired注入 public class Demo18AssemblerImpl extends Demo18AssemblerDecorator implements Demo18Assembler { } |
3.10.3 使用前后置处理实现复杂映射
使用@BeforeMapping和@AfterMapping注解可以指定映射过程的的回调方法,进行一些前置或者后置的操作。
前置回调方法的执行时机是在映射方法开始时,后置方法是在映射完成return之前。
回调方法可以直接定义在映射器内:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Mapper (componentModel = "spring" ) public interface Demo19Assembler { ProductDTO toDTO(Product product); @BeforeMapping //前置执行 default ProductDTO toDTOBefore(Product product) { ProductDTO productDTO = new ProductDTO(); productDTO.setSales( 9999 ); return productDTO; } @AfterMapping //后置执行 default void toDTOAfter(Product product, @MappingTarget ProductDTO productDTO) { productDTO.setViewName(product.getName() + "-" + product.getTitle()); } } |
生成的实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Component public class Demo19AssemblerImpl implements Demo19Assembler { @Override public ProductDTO toDTO(Product product) { ProductDTO target = toDTOBefore( product ); //前置 if ( target != null ) { return target; } if ( product == null ) { return null ; } ProductDTO productDTO = new ProductDTO(); productDTO.setProductId( product.getProductId() ); toDTOAfter( product, productDTO ); //后置 return productDTO; } } |
复制免费讲解AI专家
回调方法与映射的方法的匹配规则:
(1)映射方法和回调方法没有强绑定的关系,是依靠参数类型来匹配映射方法与回调方法的。映射方法的所有入参和出参类型,能覆盖回调方法的入参,就会调用对应的回调方法,当要注意,如果回调方法的入参是映射方法的出参类型,回调方法中需要用@MappingTarget 指定,否则不会调用。
(2)回调方法是void或者返回映射方法的出参类型才能匹配,但要注意,如果返回的是映射方法的出参类型,如果执行时返回不为null,则映射方法直接返回回调方法执行结果,不会往后执行。