目錄:
這個應該是MapStruct系列的最后一篇了!!
一、重用映射配置
1.1、映射繼承
mapStruct的映射繼承,是通過@InheritConfiguration來實現的,這個前幾篇已經提到過。這里就再不涉及了。但是需要再強調下:
1)方法級配置注解,例如@Mapping,@BeanMapping,@IterableMapping等等,都可以用注解@InheritConfiguration 繼承方法的映射;
2)如果在mapStruct的作用域范圍內,@InheritConfiguration繼承的映射關系是明確的,唯一的,那么可以忽略其name屬性;如果是不唯一,那么name屬性必須配置,不然編譯錯誤;
3)如果@InheritConfiguration繼承的映射不滿足要求,可以使用@Mapping、@BeanMapping、@IterableMapping 覆蓋原有繼承;
4)一般@InheritConfiguration是和@MappingTarget 配合使用的,類似如下:
@Mapper public interface CarMapper { @Mapping(target = "numberOfSeats", source = "seatCount") Car carDtoToCar(CarDto car); @InheritConfiguration void carDtoIntoCar(CarDto carDto, @MappingTarget Car car); }
1.2、逆映射
這個之前也提到過了,mapStruct的逆映射是通過 @InheritInverseConfiguration實現的,詳細的例子可以看之前的系列文章,這里也是同樣的再強調下:
1)如果A的結果類型與B的單一源類型相同,並且A的單一源類型與B的結果類型相同,則方法A被認為是方法B的反向方法。
2)逆繼承的方法需要在當前的映射器(一個超類/接口)中定義。
3)如果有多個方法符合條件,則需要使用name屬性指定繼承配置的方法,如下所示:@InheritInverseConfiguration(name = "xxx")。
4)mapping中存在ignore、expression、constant的映射,逆映射后需要重新配置映射。
@Mapper public interface CarMapper { @Mapping(source = "numberOfSeats", target = "seatCount") CarDto carToDto(Car car); @InheritInverseConfiguration Car carDtoToCar(CarDto carDto); }
1.3、共享配置
共享配置是新知識。舉個例子:我們經常在數據庫中定義 創建時間date_create、修改時間date_update 這樣的字段,通常情況下幾乎每個表都存在這樣的字段,數據庫里面的類型是java.sql.Timestamp類型,而我們一般都轉為String類型便於顯示。如果不用mapStruct的共享配置,那相當於在每個表對應的轉化類里面配置 Timestamp到String的映射。但是如果有共享配置,我們只要配置一遍,然后在其他地方引入,達到共享的目的。
mapStruct的共享映射通過注解 @MapperConfig定義,然后在@Mapper的屬性config中引入。@MapperConfig注釋具有與@Mapper注釋相同的屬性。任何未通過@Mapper指定的屬性都將從共享配置中繼承。在@Mapper中指定的屬性優先於通過引用的配置類指定的屬性。列表屬性,如uses是一個簡單地組合。
這個是官網的一個例子,我們可以看到,使用@mapper#config 引入后,uses,unmappedTargetPolicy 策略都繼承了。
@MapperConfig( uses = CustomMapperViaMapperConfig.class, unmappedTargetPolicy = ReportingPolicy.ERROR ) public interface CentralConfig { } @Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } ) // Effective configuration: // @Mapper( // uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class }, // unmappedTargetPolicy = ReportingPolicy.ERROR // ) public interface SourceTargetMapper { ... }
帶有@MapperConfig 注釋的接口還可以通過屬性mappingInheritanceStrategy聲明映射方法的原型,這些映射方法可用於從其中繼承方法級映射注釋。這樣的原型方法並不意味着要實現或作為mapper API的一部分使用。
@MapperConfig( uses = CustomMapperViaMapperConfig.class, unmappedTargetPolicy = ReportingPolicy.ERROR, mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG ) public interface CentralConfig { // Not intended to be generated, but to carry inheritable mapping annotations: @Mapping(target = "primaryKey", source = "technicalKey") BaseEntity anyDtoToEntity(BaseDto dto); } @Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } ) public interface SourceTargetMapper { @Mapping(target = "numberOfSeats", source = "seatCount") // additionally inherited from CentralConfig, because Car extends BaseEntity and CarDto extends BaseDto: // @Mapping(target = "primaryKey", source = "technicalKey") Car toCar(CarDto car) }
當方法級映射配置注釋從接口中的原型方法繼承到映射器中的方法時,配置屬性@Mapper#mappingInheritanceStrategy() / @MapperConfig#mappingInheritanceStrategy():
1)EXPLICIT(默認):只有當目標映射方法被注釋為@InheritConfiguration,並且源和目標類型可以指定給原型方法的相應類型時,配置才會被繼承,所有這些在映射配置繼承中都有描述。
2)AUTO_INHERIT_FROM_CONFIG:如果目標映射方法的源和目標類型可以指定給原型方法的相應類型,那么配置將被自動繼承。如果多個原型方法匹配,則必須使用@InheritConfiguration(name =…)來解決歧義。
二、自定義映射
有時需要在某些映射方法之前或之后應用自定義邏輯。MapStruct提供了兩種方法:裝飾器允許對特定映射方法進行類型安全的自定義,而映射前和映射后生命周期方法則允許對具有給定源或目標類型的映射方法進行通用的自定義。
2.1、使用裝飾器自定義映射
不同的componentModel的裝飾器形式略有不同,這里只講componentModel = default 模式的裝飾器,其他的可以參考官網。
裝飾器用到了@DecoratedWith 這個注解,裝飾器必須是裝飾的映射器類型的子類型。可以定義為抽象類。該抽象類僅允許實現您要自定義的mapper接口的那些方法。對於所有未實現的方法,將使用默認生成例程生成對原始映射器的簡單委托。
我們看個例子。我們將person轉化成了personDto,這個沒啥難度,都是之前的例子。

@Data @NoArgsConstructor @AllArgsConstructor public class Person { //主鍵 private String id; //名稱 private String name; //省 private String province; //市 private String city; //生日 private Date birthday; //電話 private String phone; } @Data @NoArgsConstructor @AllArgsConstructor public class PersonDto { //主鍵 private String id; //名稱 private String name; //地址(省+市) private String address; //生日 private Long birthday; //電話 private String phone; } @Mapper public interface PersonMapper { PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class); @Mappings({ @Mapping(target = "address",expression = "java(person.getProvince()+'-'+person.getCity())"), @Mapping(target = "birthday",expression = "java(person.getBirthday().getTime())") }) PersonDto toDto(Person person); } //PersonDto(id=001, name=張三, address=江蘇省-南京市, birthday=1596109552343, phone=17600987654) @Test public void test1(){ Person person = new Person("001","張三","江蘇省","南京市",new Date(),"17600987654"); PersonDto personDto = PersonMapper.INSTANCE.toDto(person); System.out.println(personDto); }
現在我們使用裝飾者模式去增強——定義一個抽象類,實現映射器接口。目的是對phone進行模糊處理。看到沒,這里相當於使用了一個代理,類似與Aop,我們可以在方法執行前,執行后,或者執行前后都對方法的執行進行增強。
//定義一個抽象類,實現映射器接口 public abstract class PersonMapperDecorator implements PersonMapper { private final PersonMapper delegate; public PersonMapperDecorator(PersonMapper delegate) { this.delegate = delegate; } @Override public PersonDto toDto(Person person) { PersonDto dto = delegate.toDto(person); //對電話號碼進行加密 String phone = dto.getPhone(); if (StringUtils.isNotEmpty(phone) && phone.length() >= 11) { dto.setPhone(phone.substring(0, 3) + "*****" + phone.substring(7, phone.length() - 1)); } return dto; } }
在映射器里面使用@DecorateWith引入。
@Mapper @DecoratedWith(PersonMapperDecorator.class) public interface PersonMapper { PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class); @Mappings({ @Mapping(target = "address",expression = "java(person.getProvince()+'-'+person.getCity())"), @Mapping(target = "birthday",expression = "java(person.getBirthday().getTime())") }) PersonDto toDto(Person person); }
我們看看運行結果,是不是被增強了。
//PersonDto(id=001, name=張三, address=江蘇省-南京市, birthday=1596109845802, phone=176*****765) @Test public void test1(){ Person person = new Person("001","張三","江蘇省","南京市",new Date(),"17600987654"); PersonDto personDto = PersonMapper.INSTANCE.toDto(person); System.out.println(personDto); }
看下它的實現類。
//沒有裝飾器的 映射器實現類 @Generated
public class PersonMapperImpl_ implements PersonMapper { @Override public PersonDto toDto(Person person) { if ( person == null ) { return null; } PersonDto personDto = new PersonDto(); personDto.setId( person.getId() ); personDto.setName( person.getName() ); personDto.setPhone( person.getPhone() ); personDto.setBirthday( person.getBirthday().getTime() ); personDto.setAddress( person.getProvince()+'-'+person.getCity() ); return personDto; } } //裝飾器進行引用 @Generated
public class PersonMapperImpl extends PersonMapperDecorator implements PersonMapper { private final PersonMapper delegate; public PersonMapperImpl() { this( new PersonMapperImpl_() ); } private PersonMapperImpl(PersonMapperImpl_ delegate) { super( delegate ); this.delegate = delegate; } }
2.2、@BeforeMapping與@AfterMapping自定義映射
//todo
三、null值處理
3.1、null值映射結果
//todo
3.2、null值檢查
//todo
四、異常處理
//todo
參考文檔:MapStruct 1.2.0.Final 參考指導書