Java中的MapStruct用法详解

目录
  • 1 MapStruct配置
  • 2 原理&性能
    • 2.1 实现原理
  • 3 使用方法
    • 3.1 转换器的检索
      • 3.1.1 使用Mappers工厂获取
      • 3.1.2 通过依赖注入的方式获取
    • 3.2 简单映射
      • 3.2.1 基本映射
      • 3.2.2 多源参数映射
      • 3.2.3 更新对象
    • 3.3 数据类型转换
      • 3.3.1 对于基础数据类型会进行自动隐式的转换
      • 3.3.2 指定转换格式
      • 3.3.3 属性为复杂对象的映射
      • 3.3.4 自定义转换器
      • 3.3.5 使用限定符限定使用转换方法
    • 3.4 Map的映射
    • 3.5 枚举值之间的转换
    • 3.6 定制Bean生成
    • 3.7 缺省值和常量
    • 3.8 存在继承关系的结果处理
    • 3.9 映射关系继承
    • 3.10 复杂映射的实现
      • 3.10.1 使用java表达式进行映射
      • 3.10.2 使用装饰器进行映射
      • 3.10.3 使用前后置处理实现复杂映射

1 MapStruct配置

MapStuct的使用非常简单,把对应的jar包引入即可。

<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属于第二种,在编译期间消耗少许的时间,换取运行时的高性能。

接口声明:

@Mapper
public interface ProductAssembler {
    SkuDTO toDTO(Sku sku);
}

编辑生成的class反编译

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工厂类,获取指定的类型。

@Mapper
public interface Assembler {
    //使用工厂方法获取Mapper实例
    Assembler INSTANCE = Mappers.getMapper(Assembler.class);
    ProductDTO toDTO(Product product);
}

3.1.2 通过依赖注入的方式获取

  MapStuct同时支持和其他框架结合,通过依赖注入的方式获取Mapper实例。目前支持spring和cdi。

@Mapper(componentModel = "spring")
public interface Assembler {
    ProductDTO toDTO(Product product);
}
@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注解指定。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    private String productId;
    private String name;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProductDTO implements Serializable {
    private static final long serialVersionUID = -6780322740093464581L;
    private String productId;
    private String productName;
}

  定义映射器:

@Mapper(componentModel = "spring")
public interface Assembler {
    @Mapping(source = "name", target = "productName")
    ProductDTO toDTO(Product product);
}

  生成的映射器试实现:

@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指定即可。

@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 更新对象

  映射时除了生成新的新对象外,还支持现存对象的更新:

@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等。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    private String productId;
    private Long price;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProductDTO implements Serializable {
    private static final long serialVersionUID = -6780322740093464581L;
    private int productId;
    private String price;
}

定义映射器:

@Mapper(componentModel = "spring")
public interface Assembler {
    ProductDTO toDTO(Product product);
}

生成的映射代码:

@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 实现。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    private String productId;
    private BigDecimal price;
    private String stock;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProductDTO implements Serializable {
    private static final long serialVersionUID = -6780322740093464581L;
    private String productId;
    private String price;
    private Integer stock;
}

映射器定义:

@Mapper(componentModel = "spring")
public interface Demo3Assembler {
    @Mapping(target = "price", numberFormat = "#.00元")   //BigDecimal转换成字符串
    @Mapping(target = "stock", numberFormat = "#个")      //字符串转换成int
    ProductDTO toDTO(Product product);
}

实现代码:

@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;
    }
}

测试代码:

@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的实现。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    private String productId;
    private Date saleTime;
    private String validTime;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProductDTO implements Serializable {
    private static final long serialVersionUID = -6780322740093464581L;
    private String productId;
    private String saleTime;
    private Date validTime;
}

定义映射器:

@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);
}

实现代码:

@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)如果类型相同,但是是集合类的引用,会创建一个新的集合,集合里面的所有引用进行拷贝。

@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)对象的类型不同,会检查映射器中是否存在对应的映射方法,如果存在,直接使用,否则会尝试自动创建子映射方法。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
    private String productId;
    private ProductDetail productDetail;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProductDetail {
    private String id;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProductDTO implements Serializable {
    private static final long serialVersionUID = 2184784038009791692L;
    private String productId;
    private ProductDetailDTO productDetail;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProductDetailDTO {
    private String detailId;
}

定义映射器:

@Mapper(componentModel = "spring")
public interface Demo6Assembler {
    ProductDTO toDTO(Product product);

    @Mapping(target = "detailId", source = "id")
    ProductDetailDTO toDetailDTO(ProductDetail detail);
}

生成代码:

@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注解支持跨层级的属性转换,属性可以在不同层级之间切换。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
    private String productId;
    private ProductDetail productDetail;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProductDetail {
    private String id;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProductDTO implements Serializable {
    private static final long serialVersionUID = 2184784038009791692L;
    private String productId;
    private ProductDetailDTO productDetail;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProductDetailDTO {
    private String productId;
    private String detailId;
}

定义映射器:

@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);
}

生成代码:

@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支持自定义转换器,实现类型之间的转换自定义的规则。

  一个自定义映射器可以定义多个映射方法,匹配时,是以方法的入参和出参进行匹配的。如果绑定的映射中,存在多个相同的入参和出参方法,将会报错。

  如果多个入参或者出参方法存在继承关系,将会匹配最具体的那一个方法。

@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;
}

定义映射器:

@Component
public class ImageFormater {
    public String format(List<String> images) {
        return String.join(",", images);
    }
}

绑定转换器:

@Mapper(componentModel = "spring", uses = ImageFormater.class)
public interface Demo8Assembler {
    ProductDTO toDTO(Product product);
}

  映射器实现:

@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)限定符使用自定义注解实现。

声明限定符:

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 {
}

绑定限定符到映射器的方法上面:

@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);
    }
}

映射时,绑定限定符,定位映射方法:

@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);
}

生成代码:

@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注解实现限定符的绑定。

@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);
    }
}

使用时绑定:

@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的分别映射:

@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注解。

public enum E1 {
    E1_1,
    E1_2,
    E1_3,
    ;
}

public enum E2 {
    E2_1,
    E2_2,
    E2_3,
    ;
}
@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);
}

生成代码:

@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。

定义对象工厂:

public class DTOFactory {
    public ProductDTO createDTO() {
        ProductDTO productDTO = new ProductDTO();
        productDTO.setStock(0);
        return productDTO;
    }
}

使用对象工厂:

@Mapper(componentModel = "spring", uses = DTOFactory.class) //指定使用的对象工厂
public interface Demo13Assembler {
    ProductDTO toDTO(Product product);
}

生成代码:

@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允许设置缺省值和常量,同时缺省值允许使用表达式。

注意:使用缺省值,源字段必须存在,否则缺省值不生效,否则应该使用常量。

@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);
}

  

@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注解指定真实返回的结果类型。

@Mapper(componentModel = "spring")
public interface Demo17Assembler {
    @BeanMapping(resultType = DogDTO.class) //指定返回的结果类型
    Animal toDTO(Dog dog);
}
@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标记当前方法继承其他映射方法的映射关系。会自动查找相同类型映射源、映射目标的方法进行继承,如果存在多个相同类型的方法,则需要手工指定。

@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);
}

除了正向继承规则外,还可以进行规则逆向继承,从被继承方法的目标对象映射到源对象。

@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表达式实现字段的映射。

注意要导入使用到的类。

@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);
}

生成的映射代码:

@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结合。

定义一个映射器,同时声明绑定装饰器:

@Mapper(componentModel = "spring")
@DecoratedWith(Demo18AssemblerDecorator.class) //声明绑定装饰器
public interface Demo18Assembler {
    ProductDTO toDTO(Product product);
}

定义装饰器:

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;
    }
}

生成装饰器代码:

@Component
@Primary //Primary修饰,方便使用时直接使用autowired注入
public class Demo18AssemblerImpl extends Demo18AssemblerDecorator implements Demo18Assembler {
}

3.10.3 使用前后置处理实现复杂映射

使用@BeforeMapping和@AfterMapping注解可以指定映射过程的的回调方法,进行一些前置或者后置的操作。

前置回调方法的执行时机是在映射方法开始时,后置方法是在映射完成return之前。

回调方法可以直接定义在映射器内:

@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());
    }
}

生成的实现代码如下:

@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;
    }
}

回调方法与映射的方法的匹配规则:

(1)映射方法和回调方法没有强绑定的关系,是依靠参数类型来匹配映射方法与回调方法的。映射方法的所有入参和出参类型,能覆盖回调方法的入参,就会调用对应的回调方法,当要注意,如果回调方法的入参是映射方法的出参类型,回调方法中需要用@MappingTarget 指定,否则不会调用。

(2)回调方法是void或者返回映射方法的出参类型才能匹配,但要注意,如果返回的是映射方法的出参类型,如果执行时返回不为null,则映射方法直接返回回调方法执行结果,不会往后执行。

到此这篇关于Java中的MapStruct用法详解的文章就介绍到这了,更多相关javaMapStruct用法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解Java中的mapstruct插件使用

    实体类的属性映射怎么可以少了它? 我们都知道,随着一个工程的越来越成熟,模块划分会越来越细,其中实体类一般存于 domain 之中,但 domain 工程最好不要被其他工程依赖,所以其他工程想获取实体类数据时就需要在各自工程写 model,自定义 model 可以根据自身业务需要映射相应的实体属性.这样一来,这个映射工程貌似并不简单了.阿森差点就犯难了…… 序 所以阿淼今天就要给大家安利一款叫 mapstruct 的插件,它就是专门用来处理 domin 实体类与 model 类的属性映射的,我们

  • 解析MapStruct转换javaBean时出现的诡异事件

    在项目中用到了MapStruct,对其可以转换JavaBean特别好奇,因为之前都是使用Vo的方式手动set转换,但是接触到MapStruct后感觉以前使用Vo的方式确实有点low 于是就想着去看一下,写了个demo.于是这诡异的时间就开始了 我得代码如下: 这是pom文件 这是我的两个javaBean 大家可以看到那个发灰的序列化接口,那是因为我再运行出结果是转换后的JavaBean属性为空,我以为是序列化时导致的所以我去掉了, 可是后来发现完全不是这个原因 这是我的mapper转换类 还有我

  • Java MapStruct解了对象映射的毒

    前言 MVC模式是目前主流项目的标准开发模式,这种模式下框架的分层结构清晰,主要分为Controller,Service,Dao.分层的结构下,各层之间的数据传输要求就会存在差异,我们不能用一个对象来贯穿3层,这样不符合开发规范且不够灵活. 我们常常会遇到层级之间字段格式需求不一致的情况,例如数据库中某个字段是datetime日期格式,这个时间戳在数据库中的存储值为2020-11-06 23:59:59.999999,但是传递给前端的时候要求接口返回yyyy-MM-dd的格式,或者有些数据在数据

  • Java实体映射工具MapStruct使用方法详解

    目录 1.序 2.简单用例 3.使用详解 1)关于接口注解@Mapper几种属性用法详解 2) 其他方法级别注解 总结 1.序 通常在后端开发中经常不直接返回实体Entity类,经过处理转换返回前端,前端提交过来的对象也需要经过转换Entity实体才做存储:通常使用的BeanUtils.copyProperties方法也比较粗暴,不仅效率低下(使用反射)而且仅映射相同名的属性,多数情况下还需要手动编写对应的转换方法实现. 插件MapStruct以接口方法结合注解优雅实现对象转换,MapStruc

  • Java中的MapStruct用法详解

    目录 1 MapStruct配置 2 原理&性能 2.1 实现原理 3 使用方法 3.1 转换器的检索 3.1.1 使用Mappers工厂获取 3.1.2 通过依赖注入的方式获取 3.2 简单映射 3.2.1 基本映射 3.2.2 多源参数映射 3.2.3 更新对象 3.3 数据类型转换 3.3.1 对于基础数据类型会进行自动隐式的转换 3.3.2 指定转换格式 3.3.3 属性为复杂对象的映射 3.3.4 自定义转换器 3.3.5 使用限定符限定使用转换方法 3.4 Map的映射 3.5 枚举

  • java 中 ChannelHandler的用法详解

    java 中 ChannelHandler的用法详解 前言: ChannelHandler处理一个I/O event或者拦截一个I/O操作,在它的ChannelPipeline中将其递交给相邻的下一个handler. 通过继承ChannelHandlerAdapter来代替 因为这个接口有许多的方法需要实现,你或许希望通过继承ChannelHandlerAdapter来代替. context对象 一个ChannelHandler和一个ChannelHandlerContext对象一起被提供.一个

  • Java中isAssignableFrom的用法详解

    class1.isAssignableFrom(class2) 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口.如果是则返回 true:否则返回 false.如果该 Class 表示一个基本类型,且指定的 Class 参数正是该 Class 对象,则该方法返回 true:否则返回 false. 1. class2是不是class1的子类或者子接口 2. Object是所有类的父类 一个例子搞定: package com.auuz

  • java 中的instanceof用法详解及instanceof是什么意思(推荐)

    好,应大家的要求先给大家说下在JAVA程序中instanceof是什么意思 instanceof是Java的一个二元操作符,和==,>,<是同一类东东.由于它是由字母组成的,所以也是Java的保留关键字.它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据. instanceof运算符用法 运算符是双目运算符,左面的操作元是一个对象实例,右面是一个类.当左面的对象是右面的类创建的对象时,该运算符运算的结果是true,否则是false 说明: (1).一个类的实例包括本

  • java中stringBuilder的用法详解

    String对象是不可改变的.每次使用System.String类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间.在需要对字符串执行重复修改的情况下,与创建新的String对象相关的系统开销可能会非常昂贵.如果要修改字符串而不创建新的对象,则可以使用System.Text.StringBuilder类.例如,当在一个循环中将许多字符串连接在一起时,使用StringBuilder类可以提升性能. 通过用一个重载的构造函数方法初始化变量,可以创建StringBui

  • java中throws实例用法详解

    在程序出现异常时,会有一个抛出异常的throw出现,这里我们要跟今天所讲的throws区分开.throws的作用是声明抛出,在名称上也跟throw有所不同.下面我们就throws对策概念.语法.实例带来讲解,帮助大家找到声明抛出异常的方法,具体方法如下. 1.概念 如果方法声明的是Exception类型的异常或者是Checked Exception异常,要求方法的调用处必须做处理. (1)继续使用throws向上(方法的调用处)声明 (2)使用try-catch-finally进行处理 2.语法

  • java中DelayQueue实例用法详解

    在阻塞队里中,除了对元素进行增加和删除外,我们可以把元素的删除做一个延迟的处理,即使用DelayQueue的方法.这里的删除需要一定的时间才能生效,有点类似于过期处理的理念.下面我们就DelayQueue的概念.特点进行讲解,然后在代码示例中体会DelayQueue的使用. 1.概念 是一个带有延迟时间的无界阻塞队列.队列中的元素,只有等延时时间到了,才能取出来.此队列一般用于过期数据的删除,或任务调度.以下,模拟一下定长时间的数据删除. 2.特点 (1)无边界设计 (2)添加(put)不阻塞,

  • java中@SuppressWarnings注解用法详解

    SuppressWarnings注解是jse提供的注解.作用是屏蔽一些无关紧要的警告.使开发者能看到一些他们真正关心的警告.从而提高开发者的效率 简介: java.lang.SuppressWarnings是J2SE 5.0中标准的Annotation之一.可以标注在类.字段.方法.参数.构造方法,以及局部变量上.作用:告诉编译器忽略指定的警告,不用在编译完成后出现警告信息. 使用: @SuppressWarnings("") @SuppressWarnings({}) @Suppre

  • Java中instance的用法详解

    关于对象的实例化 大家想到的通常是直接new,除了这个,还有些单实例模式,层次间调用等等 getInstance的使用: * 在主函数开始时调用,返回一个实例化对象,此对象是static的,在内存中保留着它的引用,即内存中有一块区域专门用来存放静态方法和变量, * 可以直接使用,调用多次返回同一个对象. getInstance 和 new的区别: 大部分类都可以用new,new就是通过生产一个新的实例对象,或者在栈上声明一个对象,每部分的调用 *都是用的一个新的对象 getInstance在单例

  • java中DecimalFormat四舍五入用法详解

    DecimalFormat 是 NumberFormat 的一个具体子类,用于格式化十进制数字.它可以支持不同类型的数,包括整数 (123).定点数 (123.4).科学记数法表示的数 (1.23E4).百分数 (12%) 和金额 ($123)这些内容的本地化. 下边先介绍下DecimalFormat的用法: import java.text.*; import java.util.*; public class DecimalFormatDemo { public static void ma

随机推荐