Java中的MapStruct用法詳解
1 MapStruct配置
MapStuct的使用非常簡單,把對應(yīng)的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 實現(xiàn)原理
對象拷貝工具實現(xiàn)上一般分為2種:
(1) 在運行時,通過反射調(diào)用set/get方法或者直接對成員變量進行賦值。
(2)在編譯期,生成調(diào)用get/set方法進行賦值的代碼,生成對應(yīng)的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注解,實現(xiàn)靈活的字段映射,定制映射的規(guī)則。
3.1 轉(zhuǎn)換器的檢索
在聲明好轉(zhuǎn)換接口之后,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同時支持和其他框架結(jié)合,通過依賴注入的方式獲取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 基本映射
對于同名同屬性的字段,無需特別聲明指定,自動轉(zhuǎn)換。
對于不同名相同屬性的字段,可以使用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);
}生成的映射器試實現(xiàn):
@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() ); //相同映射名自動轉(zhuǎn)換
return productDTO;
}
}3.2.2 多源參數(shù)映射
支持把多個參數(shù)映射成一個類型,使用@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 更新對象
映射時除了生成新的新對象外,還支持現(xiàn)存對象的更新:
@Mapper(componentModel = "spring")
public interface Demo6Assembler {
@Mapping(target = "desc", source = "desc")
void updateDTO(@MappingTarget ProductDTO productDTO, ProductDetail detail);
}3.3 數(shù)據(jù)類型轉(zhuǎn)換
3.3.1 對于基礎(chǔ)數(shù)據(jù)類型會進行自動隱式的轉(zhuǎn)換
如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自動轉(zhuǎn)int
productDTO.setProductId( Integer.parseInt( product.getProductId() ) );
}
if ( product.getPrice() != null ) {
//Long轉(zhuǎn)String
productDTO.setPrice( String.valueOf( product.getPrice() ) );
}
return productDTO;
}
}3.3.2 指定轉(zhuǎn)換格式
某些類型的轉(zhuǎn)換,我們可以指定具體轉(zhuǎn)換的格式。
(1)對于基本數(shù)據(jù)類型與String之間的轉(zhuǎn)換,可以使用numberFormat 指定轉(zhuǎn)換格式,使用的是java.text.DecimalFormat 實現(xiàn)。
@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轉(zhuǎn)換成字符串
@Mapping(target = "stock", numberFormat = "#個") //字符串轉(zhuǎn)換成int
ProductDTO toDTO(Product product);
}實現(xiàn)代碼:
@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之間的轉(zhuǎn)換,可以通過dateFormat指定轉(zhuǎn)換格式,使用的是SimpleDateFormat的實現(xiàn)。
@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轉(zhuǎn)換成String
@Mapping(target = "validTime", dateFormat = "yyyy-MM-dd HH:mm") //String轉(zhuǎn)換成Date
ProductDTO toDTO(Product product);
}實現(xiàn)代碼:
@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() ) ); //轉(zhuǎn)換成String
}
try {
if ( product.getValidTime() != null ) {
productDTO.setValidTime( new SimpleDateFormat( "yyyy-MM-dd HH:mm" ).parse( product.getValidTime() ) ); //轉(zhuǎn)換成Date
}
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
return productDTO;
}
}3.3.3 屬性為復(fù)雜對象的映射
(1)如果是相同類型的對象引用,不會創(chuàng)建新的對象,直接把對象的引用從源對象賦值給目標對象。
(2)如果類型相同,但是是集合類的引用,會創(chuàng)建一個新的集合,集合里面的所有引用進行拷貝。
@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 ) ); //創(chuàng)建新的集合,并對所有元素進行拷貝
}
return productDTO;
}
(3)對象的類型不同,會檢查映射器中是否存在對應(yīng)的映射方法,如果存在,直接使用,否則會嘗試自動創(chuàng)建子映射方法。
@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() ) ); //查找使用存在的轉(zhuǎn)換方法
return productDTO;
}
public ProductDetailDTO toDetailDTO(ProductDetail detail) {
if ( detail == null ) {
ProductDetailDTO productDetailDTO = new ProductDetailDTO();
productDetailDTO.setDetailId( detail.getId() );
return productDetailDTO;
}(4)多層bean之間的轉(zhuǎn)換
@Mapping注解支持跨層級的屬性轉(zhuǎn)換,屬性可以在不同層級之間切換。
@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下的屬性轉(zhuǎn)換規(guī)則
@Mapping(target = "productDetail.productId", source = "productId") //跨層級的屬性轉(zhuǎn)換,把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的轉(zhuǎn)換方法
protected void productDetailToProductDetailDTO(ProductDetail productDetail, ProductDetailDTO mappingTarget) {
if ( productDetail == null ) {
return;
mappingTarget.setDetailId( productDetail.getId() );
//product轉(zhuǎn)成detail(更新處理)
protected void productToProductDetailDTO(Product product, ProductDetailDTO mappingTarget) {
mappingTarget.setProductId( product.getProductId() );
}3.3.4 自定義轉(zhuǎn)換器
MapStruct支持自定義轉(zhuǎn)換器,實現(xiàn)類型之間的轉(zhuǎn)換自定義的規(guī)則。
一個自定義映射器可以定義多個映射方法,匹配時,是以方法的入?yún)⒑统鰠⑦M行匹配的。如果綁定的映射中,存在多個相同的入?yún)⒑统鰠⒎椒?,將會報錯。
如果多個入?yún)⒒蛘叱鰠⒎椒ù嬖诶^承關(guān)系,將會匹配最具體的那一個方法。
@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);
}
}綁定轉(zhuǎn)換器:
@Mapper(componentModel = "spring", uses = ImageFormater.class)
public interface Demo8Assembler {
ProductDTO toDTO(Product product);
}映射器實現(xiàn):
@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() );
//調(diào)用自定義的映射器進行映射,把list轉(zhuǎn)成string
productDTO.setImages( imageFormater.format( product.getImages() ) );
return productDTO;
}
}3.3.5 使用限定符限定使用轉(zhuǎn)換方法
自定義轉(zhuǎn)換器時,存在多個相同入?yún)⒑统鰠⒌姆椒ǎ琈apStruct無法匹配使用哪個映射方法。這時可以使用限定符綁定每個屬性轉(zhuǎn)換時使用的轉(zhuǎn)換方法。
(1)限定符使用自定義注解實現(xiàn)。
聲明限定符:
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) //轉(zhuǎn)換指定限定符,定位具體的映射方法
@Mapping(target = "details", qualifiedBy = FormatDetails.class)//轉(zhuǎn)換指定限定符,定位具體的映射方法
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注解實現(xiàn)(推薦)
除了使用自定義注解的方法,還可以使用@Named注解實現(xiàn)限定符的綁定。
@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") //轉(zhuǎn)換指定限定符,定位具體的映射方法
@Mapping(target = "details", qualifiedByName = "formatDetails")//轉(zhuǎn)換指定限定符,定位具體的映射方法
ProductDTO toDTO(Product product);
}3.4 Map的映射
可以使用@MapMapping實現(xiàn)對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 枚舉值之間的轉(zhuǎn)換
MapStruct可以在多個枚舉值之間轉(zhuǎn)換,使用@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") //轉(zhuǎn)換成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可以使用對象工廠來創(chuàng)建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(); //使用對象工廠創(chuàng)建對象
productDTO.setProductId( product.getProductId() );
return productDTO;
}
}3.7 缺省值和常量
MapStruct允許設(shè)置缺省值和常量,同時缺省值允許使用表達式。
注意:使用缺省值,源字段必須存在,否則缺省值不生效,否則應(yīng)該使用常量。
@Mapper(componentModel = "spring", imports = UUID.class)
public interface Demo15Assembler {
@Mapping(target = "productId", source = "productId", defaultValue = "0") //當product的productId為null,設(shè)置為0
@Mapping(target = "random", source = "random", defaultExpression = "java(UUID.randomUUID().toString())") //缺省設(shè)置隨機數(shù)
@Mapping(target = "stock", constant = "0") //固定設(shè)置為0
@Mapping(target = "createTime", dateFormat = "yyyy-MM-dd", constant = "2020-05-30") //固定格式化設(shè)置為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 存在繼承關(guān)系的結(jié)果處理
當返回的結(jié)果類型存在繼承關(guān)系時,可以使用@BeanMapping注解指定真實返回的結(jié)果類型。
@Mapper(componentModel = "spring")
public interface Demo17Assembler {
@BeanMapping(resultType = DogDTO.class) //指定返回的結(jié)果類型
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 映射關(guān)系繼承
MapStruct允許對映射關(guān)系進行繼承,使用@InheritConfiguration標記當前方法繼承其他映射方法的映射關(guān)系。會自動查找相同類型映射源、映射目標的方法進行繼承,如果存在多個相同類型的方法,則需要手工指定。
@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的映射關(guān)系進行繼承
@Mapping(target = "detail", source = "detail2") //對繼承的關(guān)系進行重寫
void update(@MappingTarget ProductDTO productDTO, Product product);
}除了正向繼承規(guī)則外,還可以進行規(guī)則逆向繼承,從被繼承方法的目標對象映射到源對象。
@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的映射關(guān)系進行逆繼承
@Mapping(target = "detail2", source = "detail") //對逆向繼承的關(guān)系進行重寫
Product toEntity(ProductDTO dto);
}3.10 復(fù)雜映射的實現(xiàn)
有時候我們除了普通映射外,還需要進行一些復(fù)雜的映射,如把多個字段計算映射成一個字段,或者借用一些工具進行映射的計算等。MapStruct提供了集中方式實現(xiàn)。
3.10.1 使用java表達式進行映射
對于復(fù)雜的映射,允許使用java表達式實現(xiàn)字段的映射。
注意要導(dǎo)入使用到的類。
@Mapper(componentModel = "spring", imports = DecimalUtils.class) //導(dǎo)入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允許使用裝飾器進行一些復(fù)雜映射,同時可以支持和Spring結(jié)合。
定義一個映射器,同時聲明綁定裝飾器:
@Mapper(componentModel = "spring")
@DecoratedWith(Demo18AssemblerDecorator.class) //聲明綁定裝飾器
public interface Demo18Assembler {
ProductDTO toDTO(Product product);
}定義裝飾器:
public abstract class Demo18AssemblerDecorator implements Demo18Assembler {
@Autowired
@Qualifier("delegate") //注入mapStruct生成的轉(zhuǎn)換器,原始的轉(zhuǎn)換器注入spring時,會使用delegate裝飾符
private Demo18Assembler assembler;
//可以獲取spring的bean進行操作
@Autowired
private StringUtils stringUtils;
@Override
public ProductDTO toDTO(Product product) {
//調(diào)用MapStruct進行轉(zhuǎn)換
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 使用前后置處理實現(xiàn)復(fù)雜映射
使用@BeforeMapping和@AfterMapping注解可以指定映射過程的的回調(diào)方法,進行一些前置或者后置的操作。
前置回調(diào)方法的執(zhí)行時機是在映射方法開始時,后置方法是在映射完成return之前。
回調(diào)方法可以直接定義在映射器內(nèi):
@Mapper(componentModel = "spring")
public interface Demo19Assembler {
ProductDTO toDTO(Product product);
@BeforeMapping //前置執(zhí)行
default ProductDTO toDTOBefore(Product product) {
ProductDTO productDTO = new ProductDTO();
productDTO.setSales(9999);
return productDTO;
}
@AfterMapping //后置執(zhí)行
default void toDTOAfter(Product product, @MappingTarget ProductDTO productDTO) {
productDTO.setViewName(product.getName() + "-" + product.getTitle());
}
}生成的實現(xiàn)代碼如下:
@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;
}
}回調(diào)方法與映射的方法的匹配規(guī)則:
(1)映射方法和回調(diào)方法沒有強綁定的關(guān)系,是依靠參數(shù)類型來匹配映射方法與回調(diào)方法的。映射方法的所有入?yún)⒑统鰠㈩愋停芨采w回調(diào)方法的入?yún)ⅲ蜁{(diào)用對應(yīng)的回調(diào)方法,當要注意,如果回調(diào)方法的入?yún)⑹怯成浞椒ǖ某鰠㈩愋?,回調(diào)方法中需要用@MappingTarget 指定,否則不會調(diào)用。
(2)回調(diào)方法是void或者返回映射方法的出參類型才能匹配,但要注意,如果返回的是映射方法的出參類型,如果執(zhí)行時返回不為null,則映射方法直接返回回調(diào)方法執(zhí)行結(jié)果,不會往后執(zhí)行。
到此這篇關(guān)于Java中的MapStruct用法詳解的文章就介紹到這了,更多相關(guān)javaMapStruct用法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
最新IDEA?2022基于JVM極致優(yōu)化?IDEA啟動速度的方法
這篇文章主要介紹了IDEA?2022最新版?基于?JVM極致優(yōu)化?IDEA?啟動速度,需要的朋友可以參考下2022-08-08
java.io.File的renameTo方法移動文件失敗的解決方案
這篇文章主要介紹了java.io.File的renameTo方法移動文件失敗的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
Mybatis 入門之MyBatis環(huán)境搭建(第一篇)
Mybatis的前身叫iBatis,本是apache的一個開源項目, 2010年這個項目由apache software foundation 遷移到了google code,并且改名為MyBatis。這篇文章主要介紹了Mybatis入門第一篇之MyBaits環(huán)境搭建,需要的朋友參考下2016-12-12

