首頁 > 軟體

Java中的MapStruct用法詳解

2022-04-06 13:01:42

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用法內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com