最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

一文讀懂MapStruct 1.5.5.Final

2023-06-30 11:43 作者:啥玩意你再說一遍  | 我要投稿

前言

我們在平時項目開發(fā)過程中會經(jīng)常這樣的情況,從數(shù)據(jù)庫中查出來的PO對象中包含很多字段,但是在業(yè)務(wù)處理過程中需要處理的DTO包含PO中的部分字段,或者返回給前端的VO為DTO的部分字段,這就需要Class類轉(zhuǎn)化,如果用構(gòu)造器或者get()/set()方法,將會寫大量冗余代碼并且容易出錯。面對這樣的場景,采用MapStruct插件處理類轉(zhuǎn)換是一個絕佳的選擇。

MapStruct 是一個注釋處理器,用于生成類型安全、高性能和無依賴的 Bean 映射代碼。

MapStruct與Project Lombok一起工作,MapStruct利用生成的getter、setter和構(gòu)造函數(shù),并使用它們來生成映射器實現(xiàn)。

1簡介

MapStruct是一個Java注釋處理器,用于生成類型安全的bean映射類。

您所要做的就是定義一個映射器接口,該接口聲明任何所需的映射方法。在編譯期間,MapStruct將生成這個接口的實現(xiàn)。這個實現(xiàn)使用普通的Java方法調(diào)用在源對象和目標(biāo)對象之間進(jìn)行映射。

2設(shè)置

2.1Maven配置

...
<properties>
? ?<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
</properties>
...
<dependencies>
? ?<dependency>
? ? ? ?<groupId>org.mapstruct</groupId>
? ? ? ?<artifactId>mapstruct</artifactId>
? ? ? ?<version>${org.mapstruct.version}</version>
? ?</dependency>
</dependencies>
...
<build>
? ?<plugins>
? ? ? ?<plugin>
? ? ? ? ? ?<groupId>org.apache.maven.plugins</groupId>
? ? ? ? ? ?<artifactId>maven-compiler-plugin</artifactId>
? ? ? ? ? ?<version>3.8.1</version>
? ? ? ? ? ?<configuration>
? ? ? ? ? ? ? ?<source>1.8</source>
? ? ? ? ? ? ? ?<target>1.8</target>
? ? ? ? ? ? ? ?<annotationProcessorPaths>
? ? ? ? ? ? ? ? ? ?<path>
? ? ? ? ? ? ? ? ? ? ? ?<groupId>org.mapstruct</groupId>
? ? ? ? ? ? ? ? ? ? ? ?<artifactId>mapstruct-processor</artifactId>
? ? ? ? ? ? ? ? ? ? ? ?<version>${org.mapstruct.version}</version>
? ? ? ? ? ? ? ? ? ?</path>
? ? ? ? ? ? ? ?</annotationProcessorPaths>
? ? ? ? ? ?</configuration>
? ? ? ?</plugin>
? ?</plugins>
</build>
...

這個 pom.xml 配置部分是 Maven 構(gòu)建工具的配置,用于指定項目的編譯插件和相關(guān)配置。

在這個配置中,我們使用了 maven-compiler-plugin 插件,并指定了插件的具體配置。下面是這個配置的解釋:

  • <groupId>org.apache.maven.plugins</groupId>:指定了插件的 GroupId,用于唯一標(biāo)識插件的組織或項目。

  • <artifactId>maven-compiler-plugin</artifactId>:指定了插件的 ArtifactId,用于唯一標(biāo)識插件的名稱。

  • <version>3.8.1</version>:指定了插件的版本號,用于確定使用的插件版本。

  • <configuration>:指定了插件的配置信息,包含了源代碼版本、目標(biāo)代碼版本和注解處理器路徑等配置項。

  • <source>1.8</source>:指定了項目的源代碼版本為 Java 1.8。

  • <target>1.8</target>:指定了項目的目標(biāo)代碼版本為 Java 1.8。

  • <annotationProcessorPaths>:指定了注解處理器的路徑。在這個配置中,我們添加了 org.mapstruct:mapstruct-processor 的注解處理器,${org.mapstruct.version} 是一個變量,表示從項目的屬性或外部配置文件中獲取的注解處理器版本。

總結(jié)來說,這個配置的目的是告訴 Maven 在構(gòu)建項目時使用 maven-compiler-plugin 插件進(jìn)行編譯,并指定了源代碼和目標(biāo)代碼的版本為 Java 1.8。同時,它還配置了 org.mapstruct:mapstruct-processor 的注解處理器,用于處理項目中使用 MapStruct 的注解。這樣,當(dāng)編譯項目時,MapStruct 注解會被正確處理,并生成對應(yīng)的映射代碼。

注解處理器通常是作為插件的一部分進(jìn)行配置,因此常常會在 <configuration> 中指定注解處理器的路徑。這是因為注解處理器是在編譯階段執(zhí)行的,并且常常需要特定的插件來識別和處理這些注解。通過在插件的配置中定義注解處理器路徑,可以確保在相應(yīng)的編譯階段執(zhí)行注解處理器。

然而,在某些情況下,注解處理器可能也作為依賴項進(jìn)行引入。它們可以像其他庫一樣在 <dependencies> 中聲明,并在項目構(gòu)建過程中使用。這種方法可能適用于一些通用的注解處理器,例如 Lombok、MapStruct 等。

如果要將注解處理器作為依賴項進(jìn)行引入,可以在 <dependencies> 下添加相應(yīng)的依賴項。例如:

<dependencies>
? ?<!-- 其他依賴項 -->
? ?<dependency>
? ? ? ?<groupId>org.mapstruct</groupId>
? ? ? ?<artifactId>mapstruct-processor</artifactId>
? ? ? ?<version>${org.mapstruct.version}</version>
? ? ? ?<scope>provided</scope>
? ?</dependency>
</dependencies>

在這個例子中,我們將 MapStruct 的注解處理器作為依賴項引入,并且將其范圍設(shè)置為 provided。這意味著該依賴項在編譯和測試時可用,但在運行時將由目標(biāo)環(huán)境提供。這種引入注解處理器的方式是為了解決在沒有特定插件的情況下,以依賴項的方式使用注解處理器。然而,對于需要特定插件支持的注解處理器,建議仍然將其配置在插件的 <configuration> 中。

2.2配置選項

MapStruct代碼生成器可以使用注釋處理器選項進(jìn)行配置。

當(dāng)直接調(diào)用javac時,這些選項以- key=value的形式傳遞給編譯器。當(dāng)通過Maven使用MapStruct時,任何處理器選項都可以在Maven處理器插件的配置中使用compilerArgs傳遞,如下所示:

...
<plugin>
? ?<groupId>org.apache.maven.plugins</groupId>
? ?<artifactId>maven-compiler-plugin</artifactId>
? ?<version>3.5.1</version>
? ?<configuration>
? ? ? ?<source>1.8</source>
? ? ? ?<target>1.8</target>
? ? ? ?<annotationProcessorPaths>
? ? ? ? ? ?<path>
? ? ? ? ? ? ? ?<groupId>org.mapstruct</groupId>
? ? ? ? ? ? ? ?<artifactId>mapstruct-processor</artifactId>
? ? ? ? ? ? ? ?<version>${org.mapstruct.version}</version>
? ? ? ? ? ?</path>
? ? ? ?</annotationProcessorPaths>
? ? ? ?<!-- due to problem in maven-compiler-plugin, for verbose mode add showWarnings -->
? ? ? ?<showWarnings>true</showWarnings>
? ? ? ?<compilerArgs>
? ? ? ? ? ?<arg>
? ? ? ? ? ? ? ?-Amapstruct.suppressGeneratorTimestamp=true
? ? ? ? ? ?</arg>
? ? ? ? ? ?<arg>
? ? ? ? ? ? ? ?-Amapstruct.suppressGeneratorVersionInfoComment=true
? ? ? ? ? ?</arg>
? ? ? ? ? ?<arg>
? ? ? ? ? ? ? ?-Amapstruct.verbose=true
? ? ? ? ? ?</arg>
? ? ? ?</compilerArgs>
? ?</configuration>
</plugin>
...

OptionPurposeDefaultmapstruct. suppressGeneratorTimestamp如果設(shè)置為' true ',將禁止在生成的映射器類中的' @Generated '注釋中創(chuàng)建時間戳。falsemapstruct.verbose如果設(shè)置為' true ', MapStruct記錄其主要決策的MapStruct。注意,在用Maven編寫代碼時,由于Maven -compiler-plugin配置中的一個問題,還需要添加' showWarnings '。falsemapstruct. suppressGeneratorVersionInfoComment如果設(shè)置為“true”,生成的映射器類中的“@Generated”注釋中的“comment”屬性的創(chuàng)建將被禁止。注釋包含有關(guān)MapStruct版本和用于注釋處理的編譯器的信息。falsemapstruct.defaultComponentModel組件模型的名稱(請參閱檢索映射器),應(yīng)該根據(jù)該名稱生成映射器。 支持的值有: default:映射器不使用組件模型,實例通常通過Mappers#getMapper(Class)來檢索。 cdi:生成的映射器是應(yīng)用程序作用域的CDI bean,可以通過@Inject進(jìn)行檢索(from javax.enterprise.context or jakarta.enterprise.context, depending on which one is available with javax.inject having priority)。 spring: 生成的映射器是一個單例作用域的Spring bean,可以通過@Autowired進(jìn)行檢索。 jsr330:生成的映射器用{@code @Named}注釋,可以通過@Inject(from javax.inject or jakarta.inject, depending which one is available with javax.inject having priority)來檢索,例如使用Spring。 jakarta:生成的映射器用{@code @Named}注釋,可以通過@Inject (from jakarta.inject)來檢索,例如使用Spring。 jakarta-cdi: 生成的映射器是應(yīng)用程序范圍(來自jakarta.enterprise.context)的CDI bean,可以通過@Inject進(jìn)行檢索。 如果通過@Mapper#componentModel()為特定的映射器提供組件模型,則來自注釋的值優(yōu)先。defaultmapstruct.defaultInjectionStrategy通過參數(shù)使用在映射器中注入的類型。這只用于基于注釋的組件模型,如CDI、Spring和JSR 330。 支持的值有: field: 依賴項將被注入到字段中。 constructor: 將生成構(gòu)造函數(shù)。依賴項將通過構(gòu)造函數(shù)注入。 當(dāng)CDI componentModel時,也會生成一個默認(rèn)構(gòu)造函數(shù)。如果通過@Mapper#injectionStrategy()為特定的映射器提供了注入策略,則注釋中的值優(yōu)先于該選項。fieldmapstruct.unmappedTargetPolicy如果映射方法的目標(biāo)對象的屬性沒有使用源值填充,則應(yīng)用默認(rèn)報告策略。 支持的值有: ERROR:任何未映射的目標(biāo)屬性將導(dǎo)致映射代碼生成失敗 ?WARN:任何未映射的目標(biāo)屬性將在構(gòu)建時引起警告 ?IGNORE:忽略未映射的目標(biāo)屬性 如果通過@Mapper#unmappedTargetPolicy()為特定的映射器提供策略,則注釋中的值優(yōu)先。如果通過@BeanMapping#unmappedTargetPolicy()為特定的bean映射提供策略,則該策略優(yōu)先于@Mapper#unmappedTargetPolicy()和選項。WARNmapstruct.unmappedSourcePolicy如果映射方法的源對象的屬性沒有使用目標(biāo)值填充,則應(yīng)用默認(rèn)報告策略。 支持的值有: ERROR:任何未映射的源屬性將導(dǎo)致映射代碼生成失敗 ?WARN:任何未映射的源屬性將在構(gòu)建時引起警告 ?IGNORE:忽略未映射的源屬性 如果通過@Mapper#unmappedSourcePolicy()為特定的映射器提供策略,則注釋中的值優(yōu)先。如果通過@BeanMapping#ignoreUnmappedSourceProperties()為特定的bean映射提供策略,則該策略優(yōu)先于@Mapper#unmappedSourcePolicy()和選項。WARNmapstruct. disableBuilders如果設(shè)置為true,那么MapStruct在進(jìn)行映射時將不使用構(gòu)建器模式。這相當(dāng)于對所有的映射器執(zhí)行@Mapper(builder = @Builder(disableBuilder = true))。false

2.3IDEA MapStruct插件

IDEA下集成MapStruct需要安裝插件。

MapStruct 插件(MapStruct Support)可以提供以下功能:

  1. 自動生成映射類:MapStruct 插件可以自動生成用于對象之間的映射的實現(xiàn)類。這樣,你可以通過指定映射規(guī)則和注解來生成映射代碼,而無需手動編寫大量的映射代碼。

  2. 顯示映射錯誤和警告:MapStruct 插件可以在編輯器中顯示映射錯誤和警告。這有助于提前發(fā)現(xiàn)潛在的問題,并在編譯之前解決它們。

  3. 提供快速修復(fù)和重構(gòu)功能:MapStruct 插件可以提供一些有用的快速修復(fù)和重構(gòu)功能,例如自動添加映射方法、自動修復(fù)映射錯誤等。這可以幫助提高開發(fā)效率并減少手動操作。

3定義映射器

3.1映射

要創(chuàng)建一個映射器,只需定義一個帶有所需映射方法的Java接口,并用org.mapstruct.Mapper注釋對其進(jìn)行注釋:

@Mapper
public interface CarMapper {

? ?@Mapping(target = "manufacturer", source = "make")
? ?@Mapping(target = "seatCount", source = "numberOfSeats")
? ?CarDto carToCarDto(Car car);

? ?@Mapping(target = "fullName", source = "name")
? ?PersonDto personToPersonDto(Person person);
}

@Mapper注釋導(dǎo)致MapStruct代碼生成器在構(gòu)建時創(chuàng)建CarMapper接口的實現(xiàn)。

在生成的方法實現(xiàn)中,源類型(例如Car)的所有可讀屬性將被復(fù)制到目標(biāo)類型(例如CarDto)的相應(yīng)屬性中:

  • 當(dāng)屬性與其對應(yīng)的目標(biāo)實體具有相同的名稱時,它將被隱式映射。(也就是不需要寫@Mapping(target = "manufacturer", source = "make")這樣的映射關(guān)系代碼)

  • 當(dāng)屬性在目標(biāo)實體中具有不同的名稱時,可以通過@Mapping注釋指定其名稱。

JavaBeans規(guī)范中定義的屬性名稱必須在@Mapping注釋中指定,例如,使用accessor方法getSeatCount()和setSeatCount()來指定屬性的seatCount。

通過@BeanMapping(ignoreByDefault = true),默認(rèn)行為將是顯式映射,這意味著所有映射都必須通過@Mapping來指定,并且不會對丟失的目標(biāo)屬性發(fā)出警告。這允許忽略所有字段,除了那些通過@Mapping顯式定義的字段。

@BeanMapping 用于指定在對象映射過程中自定義映射規(guī)則的注解。它可以用于方法級別或類級別。

在方法級別,@BeanMapping 注解應(yīng)用于映射方法上,用于指定源對象和目標(biāo)對象之間的映射規(guī)則。它可以包含以下屬性:

  • ignore:指定是否忽略映射規(guī)則,默認(rèn)為 false。當(dāng)設(shè)置為 true 時,表示忽略該映射規(guī)則,不生成相應(yīng)的映射代碼。

  • nullValuePropertyMappingStrategy: 指定如何映射源對象的 null 值,默認(rèn)為 NullValuePropertyMappingStrategy.SET_TO_NULL??梢栽O(shè)置為以下值:

    • NullValuePropertyMappingStrategy.SET_TO_NULL:將目標(biāo)屬性設(shè)置為 null。

    • NullValuePropertyMappingStrategy.STRICT:如果源屬性為 null,則引發(fā)異常。

    • NullValuePropertyMappingStrategy.IGNORE:忽略源屬性為 null 的情況。

  • nullValueCheckStrategy: 指定如何進(jìn)行源對象的 null 值檢查,默認(rèn)為 NullValueCheckStrategy.ALWAYS。可以設(shè)置為以下值:

    • NullValueCheckStrategy.ALWAYS:始終檢查源對象是否為 null。

    • NullValueCheckStrategy.ON_IMPLICIT_CONVERSION:僅在存在隱式轉(zhuǎn)換的情況下才檢查源對象是否為 null。

    • NullValueCheckStrategy.ON_IMPLICIT_CONVERSION_AND_EXPLICIT:在存在隱式轉(zhuǎn)換或顯式轉(zhuǎn)換的情況下才檢查源對象是否為 null。

    • NullValueCheckStrategy.NEVER:永不檢查源對象是否為 null。

在類級別,@BeanMapping 注解應(yīng)用于映射接口上,用于指定默認(rèn)的映射規(guī)則。類級別的 @BeanMapping 注解可以包含與方法級別注解相同的屬性,用于指定默認(rèn)的映射規(guī)則,這些規(guī)則將適用于該映射接口中的所有映射方法。

  • Spring框架中使用映射

要在 Spring 中使用 MapStruct 的映射,你可以按照以下步驟引入映射到 Spring 的 @Service 中:

  1. 首先,確保你已經(jīng)在項目的依賴中添加了 MapStruct 的相關(guān)依賴。這通常包括 mapstructmapstruct-processor

  2. 創(chuàng)建一個 @Mapper 注解的接口,例如 CarMapper,并在方法上使用 MapStruct 的相關(guān)注解,如 @Mapping。

  3. CarMapper 接口的實現(xiàn)類中添加 @Mapper(componentModel = "spring") 注解。這將告訴 MapStruct 在生成實現(xiàn)類時使用 Spring 的組件模型,以便將生成的實現(xiàn)類作為 Spring 的組件進(jìn)行管理。

@Mapper(componentModel = "spring")
public interface CarMapper {
? ?// 映射方法
}

4.在 @Service 類中引入 CarMapper 作為依賴,并使用 @Autowired@Resource 進(jìn)行注入。

@Service
public class CarService {
? ?private final CarMapper carMapper;

? ?@Autowired
? ?public CarService(CarMapper carMapper) {
? ? ? ?this.carMapper = carMapper;
? ?}
? ?// 使用 CarMapper 進(jìn)行映射
}

現(xiàn)在,你可以在 CarService 中使用 carMapper 進(jìn)行對象的映射操作。Spring 將會自動注入 carMapper 的實例,并通過 MapStruct 提供的映射規(guī)則將源對象映射到目標(biāo)對象。這樣,你就可以在 @Service 類中方便地使用 MapStruct 進(jìn)行對象之間的轉(zhuǎn)換。

  • 非Spring框架中使用映射

    創(chuàng)建一個工廠類,在該類中獲取 CarMapper 的實例。示例如下:

    public class CarMapperFactory {

    ? ?private static final CarMapper MAPPER = Mappers.getMapper(CarMapper.class);

    ? ?public static CarMapper getCarMapper() {
    ? ? ? ?return MAPPER;
    ? ?}
    }

    在代碼中,通過工廠類獲取 CarMapper 實例,并使用它進(jìn)行對象之間的映射。

    CarMapper carMapper = CarMapperFactory.getCarMapper();
    CarDto carDto = carMapper.carToCarDto(car);

或者直接在映射接口中添加屬性

CarMapper MAPPER = Mappers.getMapper(CarMapper.class);

3.2映射實現(xiàn)

為了更好地理解MapStruct做了什么,看看下面由MapStruct生成的carToCarDto()方法的實現(xiàn):

// GENERATED CODE
public class CarMapperImpl implements CarMapper {

? ?@Override
? ?public CarDto carToCarDto(Car car) {
? ? ? ?if ( car == null ) {
? ? ? ? ? ?return null;
? ? ? ?}

? ? ? ?CarDto carDto = new CarDto();

? ? ? ?if ( car.getFeatures() != null ) {
? ? ? ? ? ?carDto.setFeatures( new ArrayList<String>( car.getFeatures() ) );
? ? ? ?}
? ? ? ?carDto.setManufacturer( car.getMake() );
? ? ? ?carDto.setSeatCount( car.getNumberOfSeats() );
? ? ? ?carDto.setDriver( personToPersonDto( car.getDriver() ) );
? ? ? ?carDto.setPrice( String.valueOf( car.getPrice() ) );
? ? ? ?if ( car.getCategory() != null ) {
? ? ? ? ? ?carDto.setCategory( car.getCategory().toString() );
? ? ? ?}
? ? ? ?carDto.setEngine( engineToEngineDto( car.getEngine() ) );

? ? ? ?return carDto;
? ?}

? ?@Override
? ?public PersonDto personToPersonDto(Person person) {
? ? ? ?//...
? ?}

? ?private EngineDto engineToEngineDto(Engine engine) {
? ? ? ?if ( engine == null ) {
? ? ? ? ? ?return null;
? ? ? ?}

? ? ? ?EngineDto engineDto = new EngineDto();

? ? ? ?engineDto.setHorsePower(engine.getHorsePower());
? ? ? ?engineDto.setFuel(engine.getFuel());

? ? ? ?return engineDto;
? ?}
}

MapStruct的一般理念是生成看起來盡可能像您自己手寫的代碼,通過簡單的getter/setter調(diào)用而不是反射或類似的方法將值從源復(fù)制到目標(biāo)。

觀察上述代碼

carDto.setDriver( personToPersonDto( car.getDriver() ) );

也就是說MapStruct實現(xiàn)類屬性映射,是根據(jù)源類與目標(biāo)類的屬性名稱來匹配的。在CarDTO.class中存在private PersonDto driver;成員屬性,在Car.class中存在private Person driver;成員屬性。

如示例所示,生成的代碼會考慮通過@Mapping指定的任何名稱映射。如果映射屬性的類型在源實體和目標(biāo)實體中不同(例如price屬性,請參見隱式類型轉(zhuǎn)換),MapStruct將應(yīng)用自動轉(zhuǎn)換或可選地調(diào)用/創(chuàng)建另一個映射方法(例如,對于driver / engine屬性)。只有當(dāng)且僅當(dāng)源和目標(biāo)屬性是Bean的屬性并且它們本身是Bean或簡單屬性時,MapStruct才會創(chuàng)建一個新的映射方法。

3.3映射器添加自定義方法

在某些情況下,可能需要手動實現(xiàn)從一種類型到另一種類型的特定映射,而這種映射不能由MapStruct生成。處理這個問題的一種方法是在另一個類上實現(xiàn)自定義方法,然后由MapStruct生成的映射器使用。

另外,當(dāng)使用Java 8或更高版本時,您可以直接在映射器接口中實現(xiàn)自定義方法作為默認(rèn)方法。如果參數(shù)和返回類型匹配,生成的代碼將調(diào)用默認(rèn)方法。

作為一個例子,讓我們假設(shè)從Person到PersonDto的映射需要一些特殊的邏輯,這些邏輯不能由MapStruct生成。然后你可以像這樣從前面的例子中定義映射器,這樣Person.class轉(zhuǎn)PersonDto.class將按照自定義邏輯轉(zhuǎn)換。

@Mapper
public interface CarMapper {

? ?@Mapping(...)
? ?...
? ?CarDto carToCarDto(Car car);

? ?default PersonDto personToPersonDto(Person person) {
? ? ? ?//hand-written mapping logic
? ?}
}

由MapStruct生成的類實現(xiàn)了方法carToCarDto()。當(dāng)映射驅(qū)動屬性時,在carToCarDto()中生成的代碼將調(diào)用手動實現(xiàn)的personToPersonDto()方法。

MapStruct將生成CarMapper的一個子類,并實現(xiàn)carcardto()方法,因為它被聲明為抽象的。當(dāng)映射驅(qū)動屬性時,在carToCarDto()中生成的代碼將調(diào)用手動實現(xiàn)的personToPersonDto()方法。

3.4具有多個源參數(shù)的映射方法

MapStruct還支持帶有多個源參數(shù)的映射方法。例如,為了將幾個實體組合成一個數(shù)據(jù)傳輸對象。示例如下:

@Mapper
public interface AddressMapper {

? ?@Mapping(target = "description", source = "person.description")
? ?@Mapping(target = "houseNumber", source = "address.houseNo")
? ?DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}

所示的映射方法接受兩個源參數(shù),并返回一個組合的目標(biāo)對象。與單參數(shù)映射方法一樣,屬性是按名稱映射的。

如果多個源對象定義具有相同名稱的屬性,則必須使用@Mapping注釋指定從中檢索屬性的源參數(shù),如示例中的description屬性所示。當(dāng)這種歧義未解決時將引發(fā)錯誤。對于在給定源對象中只存在一次的屬性,指定源參數(shù)的名稱是可選的,因為它可以自動確定。

在使用@Mapping注釋時,必須指定屬性所在的參數(shù)。

如果所有源參數(shù)都為空,具有多個源參數(shù)的映射方法將返回空。否則,將實例化目標(biāo)對象,并傳播來自所提供參數(shù)的所有屬性。

MapStruct還提供了直接引用源參數(shù)的可能性。

@Mapper
public interface AddressMapper {

? ?@Mapping(target = "description", source = "person.description")
? ?@Mapping(target = "houseNumber", source = "hn")
? ?DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn);
}

在本例中,source參數(shù)直接映射到目標(biāo),如上面的示例所示。參數(shù)hn,一個非bean類型(在本例中為java.lang.Integer)被映射到houseNumber。

3.5將嵌套bean屬性映射到當(dāng)前目標(biāo)

如果不想顯式地命名嵌套源bean中的所有屬性,可以使用.作為目標(biāo)。這將告訴MapStruct將每個屬性從源bean映射到目標(biāo)對象。示例如下:

@Mapper
public interface CustomerMapper {

? ? @Mapping( target = "name", source = "record.name" )
? ? @Mapping( target = ".", source = "record" )
? ? @Mapping( target = ".", source = "account" )
? ? Customer customerDtoToCustomer(CustomerDto customerDto);
}

生成的代碼將把每個屬性從CustomerDto.record直接映射到Customer,而不需要手動命名它們中的任何一個。對于Customer.account也是如此。

當(dāng)存在沖突時,可以通過顯式定義映射來解決這些沖突。例如在上面的例子中。name出現(xiàn)在CustomerDto中。record在CustomerDto.account中。映射@Mapping(target = "name", source = "record.name")解決了這個沖突。

這種“target this”符號在將分層對象映射到平面對象時非常有用,反之亦然(@InheritInverseConfiguration)。

3.6更新現(xiàn)有的bean實例

在某些情況下,您需要的映射不是創(chuàng)建目標(biāo)類型的新實例,而是更新該類型的現(xiàn)有實例。這種映射可以通過為目標(biāo)對象添加一個參數(shù)并使用@MappingTarget標(biāo)記該參數(shù)來實現(xiàn)。示例如下:

@Mapper
public interface CarMapper {
? ?void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
}

updateCarFromDto()方法生成的代碼將使用給定CarDto對象的屬性更新傳入的Car實例??赡苤挥幸粋€參數(shù)被標(biāo)記為映射目標(biāo)。除了void之外,您還可以將方法的返回類型設(shè)置為目標(biāo)參數(shù)的類型,這將導(dǎo)致生成的實現(xiàn)更新傳遞的映射目標(biāo)并返回它。

3.7直接字段映射

MapStruct還支持沒有g(shù)etter /setter的公共字段的映射。如果MapStruct不能為屬性找到合適的getter/setter方法,它將使用這些字段作為讀/寫訪問器。

如果字段是public或public final,則將其視為讀訪問器。如果字段是靜態(tài)的,則不將其視為讀訪問器。

只有當(dāng)字段是公共的時,它才被視為寫訪問器。如果字段是final和/或靜態(tài)的,則不將其視為寫訪問器。

public class Customer {

? ?private Long id;
? ?private String name;

? ?//為簡潔起見,省略了getter和setter
}

public class CustomerDto {

? ?public Long id;
? ?public String customerName;
}

@Mapper
public interface CustomerMapper {

? ?CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class );

? ?@Mapping(target = "name", source = "customerName")
? ?Customer toCustomer(CustomerDto customerDto);

// @InheritInverseConfiguration 是 MapStruct 中的一個注解,用于繼承相反的映射配置。它通常與 ?
? ?// @Mapping 注解一起使用,用于聲明映射配置的相反關(guān)系。
? ?@InheritInverseConfiguration
? ?CustomerDto fromCustomer(Customer customer);
}

上述代碼中Customer類中存在getter和setter方法,CustomerDto中不存在getter和setter方法。MapStruct生成實現(xiàn)類代碼為

package com.example.convert;

import com.example.model.Customer;
import com.example.model.CustomerDto;
import javax.annotation.Generated;

@Generated(
? ?value = "org.mapstruct.ap.MappingProcessor",
? ?date = "2023-06-29T19:57:23+0800",
? ?comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_181 (Oracle Corporation)"
)
public class CustomerMapperImpl implements CustomerMapper {

? ?@Override
? ?public Customer toCustomer(CustomerDto customerDto) {
? ? ? ?if ( customerDto == null ) {
? ? ? ? ? ?return null;
? ? ? ?}

? ? ? ?Customer customer = new Customer();

? ? ? ?customer.setName( customerDto.customerName );
? ? ? ?customer.setId( customerDto.id );

? ? ? ?return customer;
? ?}

? ?@Override
? ?public CustomerDto fromCustomer(Customer customer) {
? ? ? ?if ( customer == null ) {
? ? ? ? ? ?return null;
? ? ? ?}

? ? ? ?CustomerDto customerDto = new CustomerDto();

? ? ? ?customerDto.customerName = customer.getName();
? ? ? ?customerDto.id = customer.getId();

? ? ? ?return customerDto;
? ?}
}

3.8使用Builder

MapStruct還支持通過構(gòu)建器映射不可變類型。在執(zhí)行映射時,MapStruct檢查所映射的類型是否有構(gòu)造器。這是通過BuilderProvider SPI完成的。如果存在特定類型的生成器,則該生成器將用于映射。

  • 具有Builder的類

public class Person {

? ?private final String name;

? ?protected Person(Person.Builder builder) {
? ? ? ?this.name = builder.name;
? ?}

? ?public static Person.Builder builder() {
? ? ? ?return new Person.Builder();
? ?}

? ?public static class Builder {

? ? ? ?private String name;

? ? ? ?public Builder name(String name) {
? ? ? ? ? ?this.name = name;
? ? ? ? ? ?return this;
? ? ? ?}

? ? ? ?public Person create() {
? ? ? ? ? ?return new Person( this );
? ? ? ?}
? ?}
}

  • 創(chuàng)建映射

public interface PersonMapper {
? ?Person map(PersonDto dto);
}

  • Mapstruct生成的實現(xiàn)類代碼

// GENERATED CODE
public class PersonMapperImpl implements PersonMapper {

? ?public Person map(PersonDto dto) {
? ? ? ?if (dto == null) {
? ? ? ? ? ?return null;
? ? ? ?}

? ? ? ?Person.Builder builder = Person.builder();

? ? ? ?builder.name( dto.getName() );

? ? ? ?return builder.create();
? ?}
}

3.9使用構(gòu)造器

MapStruct支持使用構(gòu)造函數(shù)映射目標(biāo)類型。在進(jìn)行映射時,MapStruct檢查所映射的類型是否有構(gòu)造器。如果沒有構(gòu)造器,那么MapStruct將查找單個可訪問的構(gòu)造器。當(dāng)有多個構(gòu)造函數(shù)時,執(zhí)行以下操作來選擇應(yīng)該使用的構(gòu)造函數(shù):

  • 如果構(gòu)造函數(shù)使用名為@Default的注釋,它將被使用。

  • 如果存在單個公共構(gòu)造函數(shù),則將使用它來構(gòu)造對象,而其他非公共構(gòu)造函數(shù)將被忽略。

  • 如果存在無參數(shù)構(gòu)造函數(shù),則將使用它來構(gòu)造對象,而忽略其他構(gòu)造函數(shù)。

  • 如果有多個符合條件的構(gòu)造函數(shù),則會由于構(gòu)造函數(shù)不明確而導(dǎo)致編譯錯誤。為了消除歧義,可以使用名為@Default的注釋。

public class Vehicle {

? ?protected Vehicle() { }

? ?// MapStruct將使用這個構(gòu)造函數(shù),因為它是一個單一的公共構(gòu)造函數(shù)
? ?public Vehicle(String color) { }
}

public class Car {

? ?// MapStruct將使用這個構(gòu)造函數(shù),因為它是一個無參數(shù)的空構(gòu)造函數(shù)
? ?public Car() { }

? ?public Car(String make, String color) { }
}

public class Truck {

? ?public Truck() { }

? ?// MapStruct將使用這個構(gòu)造函數(shù),因為它帶有@Default注釋
? ?@Default
? ?public Truck(String make, String color) { }
}

public class Van {

? ?// 使用這個類時會出現(xiàn)編譯錯誤,因為MapStruct不能選擇構(gòu)造函數(shù),需要有無參構(gòu)造器或者@Default注釋構(gòu)造器
? ?
? ?public Van(String make) { }

? ?public Van(String make, String color) { }

}

當(dāng)使用構(gòu)造函數(shù)時,將使用構(gòu)造函數(shù)的參數(shù)名并將其與目標(biāo)屬性匹配。當(dāng)構(gòu)造函數(shù)有一個名為@ConstructorProperties的注釋時,該注釋將用于獲取參數(shù)的名稱。

  • 具有構(gòu)造函數(shù)的Person類

public class Person {

? ?private final String name;
? ?private final String surname;

? ?public Person(String name, String surname) {
? ? ? ?this.name = name;
? ? ? ?this.surname = surname;
? ?}
}

  • 創(chuàng)建映射

public interface PersonMapper {

? ?Person map(PersonDto dto);
}

  • MapStruct生成映射代碼實現(xiàn)

// GENERATED CODE
public class PersonMapperImpl implements PersonMapper {

? ?public Person map(PersonDto dto) {
? ? ? ?if (dto == null) {
? ? ? ? ? ?return null;
? ? ? ?}

? ? ? ?String name;
? ? ? ?String surname;
? ? ? ?name = dto.getName();
? ? ? ?surname = dto.getSurname();

? ? ? ?Person person = new Person( name, surname );

? ? ? ?return person;
? ?}
}

3.10Map映射Bean

在某些情況下,需要從Map<String, ?>映射到特定bean。MapStruct通過使用目標(biāo)bean屬性(或通過mapping #source定義)從映射中提取值,提供了一種透明的方式來進(jìn)行這種映射。這樣的映射看起來像:

public class Customer {

? ?private Long id;
? ?private String name;

? ?//getters and setter omitted for brevity
}

@Mapper
public interface CustomerMapper {

? ?@Mapping(target = "name", source = "customerName")
? ?Customer toCustomer(Map<String, String> map);

}

MapStruct生成的映射實現(xiàn)類

// GENERATED CODE
public class CustomerMapperImpl implements CustomerMapper {

? ?@Override
? ?public Customer toCustomer(Map<String, String> map) {
? ? ? ?// ...
? ? ? ?if ( map.containsKey( "id" ) ) {
? ? ? ? ? ?customer.setId( Integer.parseInt( map.get( "id" ) ) );
? ? ? ?}
? ? ? ?if ( map.containsKey( "customerName" ) ) {
? ? ? ? ? ?customer.setName( map.get( "customerName" ) );
? ? ? ?}
? ? ? ?// ...
? ?}
}

4檢索映射器

所謂檢索映射器,其實就是如何在不同的框架中使用映射器。

4.1映射器工廠(沒有依賴注入)

當(dāng)不使用DI框架時,可以通過org.mapstruct.factory.Mappers類檢索Mapper實例。只需調(diào)用getMapper()方法,傳遞映射器的接口類型以返回:

CarMapper mapper = Mappers.getMapper( CarMapper.class );

按照約定,mapper接口應(yīng)該定義一個名為INSTANCE的成員,該成員包含mapper類型的單個實例:

@Mapper
public interface CarMapper {

? ?CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );

? ?CarDto carToCarDto(Car car);
}@Mapper
public abstract class CarMapper {

? ?public static final CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );

? ?CarDto carToCarDto(Car car);
}

他的模式使得客戶端很容易使用mapper對象,而無需重復(fù)實例化新實例:

Car car = ...;
CarDto dto = CarMapper.INSTANCE.carToCarDto( car );

注意,由MapStruct生成的映射器是無狀態(tài)和線程安全的,因此可以安全地從多個線程同時訪問。

4.2使用依賴注入

這里可以參考3.1,是一種更常用的方法。

如果您正在使用依賴注入框架,如CDI (java EE的上下文和依賴注入)或Spring框架,建議通過依賴注入獲得映射器對象,而不是像上面描述的那樣通過Mappers類。為此,你可以通過@Mapper#componentModel指定生成mapper類的組件模型,或者使用配置選項中描述的處理器選項。

目前有對CDI和Spring的支持(后者要么通過自定義注解,要么使用JSR 330注解)。關(guān)于componentModel屬性允許的值,請參見配置選項,這些值與mapstruct.defaultComponentModel處理器選項相同,常量在類MappingConstants.ComponentModel中定義。在這兩種情況下,所需的注釋都將被添加到生成的映射器實現(xiàn)類中,以便將相同的主題用于依賴注入。使用CDI的示例如下:

@Mapper(componentModel = MappingConstants.ComponentModel.CDI)
public interface CarMapper {

? ?CarDto carToCarDto(Car car);
}

生成的映射器實現(xiàn)將被標(biāo)記為@ApplicationScoped注釋,因此可以使用@Inject注釋注入字段、構(gòu)造函數(shù)參數(shù)等:

@Inject
private CarMapper mapper;

使用其他映射器類(參見調(diào)用其他映射器)的映射器將使用配置的組件模型獲得這些映射器。因此,如果前面示例中的CarMapper使用了另一個映射器,那么這個映射器也必須是一個可注入的CDI bean。

4.3注入策略

使用依賴注入時,可以在字段注入和構(gòu)造函數(shù)注入之間進(jìn)行選擇。這可以通過@Mapper或@MapperConfig注釋提供注入策略來實現(xiàn)。

@Mapper(componentModel = MappingConstants.ComponentModel.CDI, uses = EngineMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface CarMapper {
? ?CarDto carToCarDto(Car car);
}

如果MapStruct檢測到需要為映射使用它的一個實例,那么生成的映射器將注入在uses屬性中定義的類。當(dāng)使用InjectionStrategy#CONSTRUCTOR時,構(gòu)造函數(shù)將具有適當(dāng)?shù)淖⑨?,而字段則沒有。當(dāng)使用InjectionStrategy#FIELD時,注釋是在字段本身上。目前,默認(rèn)的注入策略是字段注入,但也可以通過Configuration選項進(jìn)行配置。建議使用構(gòu)造函數(shù)注入來簡化測試。

5數(shù)據(jù)類型轉(zhuǎn)換

映射屬性在源對象和目標(biāo)對象中并不總是具有相同的類型。例如,一個屬性在源bean中可能是int類型,但在目標(biāo)bean中可能是Long類型。

另一個例子是對其他對象的引用,這些對象應(yīng)該映射到目標(biāo)模型中的相應(yīng)類型。例如,Car類可能有一個Person類型的屬性驅(qū)動程序,在映射Car對象時需要將其轉(zhuǎn)換為PersonDto對象。

在本節(jié)中,您將了解MapStruct如何處理此類數(shù)據(jù)類型轉(zhuǎn)換。

5.1隱式類型轉(zhuǎn)換

在許多情況下,MapStruct會自動處理類型轉(zhuǎn)換。例如,如果一個屬性在源bean中是int類型,而在目標(biāo)bean中是String類型,那么生成的代碼將分別通過調(diào)用string# valueOf(int)和Integer#parseInt(String)透明地執(zhí)行轉(zhuǎn)換。

目前自動應(yīng)用以下轉(zhuǎn)換:

  • 在所有Java基本數(shù)據(jù)類型和它們對應(yīng)的包裝類型之間,例如int和Integer, boolean和Boolean等。生成的代碼是空感知的,即當(dāng)將包裝器類型轉(zhuǎn)換為相應(yīng)的原語類型時,將執(zhí)行空檢查。

  • 在所有Java基本數(shù)字類型和包裝類型之間,例如在int和long或byte和Integer之間。

從較大的數(shù)據(jù)類型轉(zhuǎn)換為較小的數(shù)據(jù)類型(例如從long到int)可能會導(dǎo)致值或精度損失。Mapper和MapperConfig注釋有一個方法typeConversionPolicy來控制警告/錯誤。由于向后兼容性的原因,默認(rèn)值是ReportingPolicy.IGNORE。

  • 在所有Java基本類型(包括它們的包裝器)和String之間,例如在int和String或Boolean和String之間。可以指定java.text.DecimalFormat所轉(zhuǎn)換的格式字符串。

@Mapper
public interface CarMapper {

? ?@Mapping(source = "price", numberFormat = "$#.00")
? ?CarDto carToCarDto(Car car);

? ?@IterableMapping(numberFormat = "$#.00")
? ?List<String> prices(List<Integer> prices);
}

  • 在大數(shù)類型之間(java.math.BigInteger, Java.math.BigDecimal)和Java基本類型(包括它們的包裝器)以及String。可以指定java.text.DecimalFormat所理解的格式字符串。

@Mapper
public interface CarMapper {

? ?@Mapping(source = "power", numberFormat = "#.##E0")
? ?CarDto carToCarDto(Car car);

}

  • Java.util.Date/XMLGregorianCalendar和String之間。java.text.SimpleDateFormat所理解的格式字符串可以通過dateFormat選項指定,如下所示:

@Mapper
public interface CarMapper {

? ?@Mapping(source = "manufacturingDate", dateFormat = "dd.MM.yyyy")
? ?CarDto carToCarDto(Car car);

? ?@IterableMapping(dateFormat = "dd.MM.yyyy")
? ?List<String> stringListToDateList(List<Date> dates);
}

5.2映射對象引用

通常,一個對象不僅具有基本屬性,而且還引用其他對象。例如,Car類可以包含對Person對象(代表汽車的司機(jī))的引用,該對象應(yīng)該映射到由CarDto類引用的PersonDto對象。

在這種情況下,只需為引用的對象類型定義一個映射方法即可:

@Mapper
public interface CarMapper {

? ?CarDto carToCarDto(Car car);

? ?PersonDto personToPersonDto(Person person);
}

引用的映射可以參考MapStruct生成的轉(zhuǎn)換代碼的實現(xiàn)。

5.3控制嵌套bean映射

如上所述,MapStruct將根據(jù)源屬性和目標(biāo)屬性的名稱生成一個方法。不幸的是,在許多情況下,這些名稱并不匹配。

@Mapping源或目標(biāo)類型中的“.”符號可用于控制當(dāng)名稱不匹配時應(yīng)該如何映射屬性。在我們的示例存儲庫中有一個詳細(xì)的示例來解釋如何克服這個問題。

在最簡單的場景中,有一個嵌套級別上的屬性需要糾正。以屬性fish為例,它在FishTankDto和FishTank中具有相同的名稱。對于這個屬性,MapStruct自動生成一個映射:FishDto fishToFishDto(Fish Fish)。MapStruct不可能知道偏離的屬性類型。因此,這可以在映射規(guī)則中解決:@Mapping(target="fish.kind", source="fish.type")。這告訴MapStruct不要在這一層尋找名稱類型,而是將其映射到類型。

@Mapper
public interface FishTankMapper {

? ?@Mapping(target = "fish.kind", source = "fish.type")
? ?@Mapping(target = "fish.name", ignore = true)
? ?@Mapping(target = "ornament", source = "interior.ornament")
? ?@Mapping(target = "material.materialType", source = "material")
? ?@Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName")
? ?FishTankDto map( FishTank source );
}

可以使用 java() 表達(dá)式,你可以在映射過程中執(zhí)行一些自定義的 Java 代碼邏輯,constant為目標(biāo)對象屬性指定常量值。

@Mapper
public interface FishTankMapperWithDocument {

? ?@Mapping(target = "fish.kind", source = "fish.type")
? ?@Mapping(target = "fish.name", expression = "java(\"Jaws\")")
? ?@Mapping(target = "plant", ignore = true )
? ?@Mapping(target = "ornament", ignore = true )
? ?@Mapping(target = "material", ignore = true)
? ?@Mapping(target = "quality.document", source = "quality.report")
? ?@Mapping(target = "quality.document.organisation.name", constant = "NoIdeaInc" )
? ?FishTankWithNestedDocumentDto map( FishTank source );
}

在提供的代碼中,expression = "java(\"Jaws\")"constant = "NoIdeaInc" 是 MapStruct 中的表達(dá)式和常量映射的用法,用于指定目標(biāo)對象屬性的值。

  • expression = "java(\"Jaws\")":這個表達(dá)式指定了目標(biāo)對象的 fish.name 屬性的值。使用 java() 表達(dá)式,你可以在映射過程中執(zhí)行一些自定義的 Java 代碼邏輯。在這個例子中,"Jaws" 是一個硬編碼的值,表示魚的名稱將始終為 "Jaws"

  • constant = "NoIdeaInc":這個常量指定了目標(biāo)對象的 quality.document.organisation.name 屬性的值。使用 constant 配置項,你可以將目標(biāo)對象屬性的值直接設(shè)置為提供的常量值。在這個例子中,"NoIdeaInc" 是一個字符串常量,它將始終作為 quality.document.organisation.name 屬性的值。

5.4調(diào)用自定義映射方法

有時映射并不直接,有些字段需要自定義邏輯。

下面的示例演示了如何將FishTank中的屬性長度、寬度和高度映射到VolumeDto bean,它是FishTankWithVolumeDto的成員。VolumeDto包含屬性volume和description。自定義邏輯是通過定義一個方法來實現(xiàn)的,該方法以魚缸實例作為參數(shù)并返回一個VolumeDto。MapStruct將獲取整個參數(shù)源并生成代碼來調(diào)用自定義方法mapVolume,以便將魚缸對象映射到目標(biāo)屬性卷。

其余的字段可以用常規(guī)的方式進(jìn)行映射:使用通過@Mapping注釋定義的映射。

public class FishTank {
? ?Fish fish;
? ?String material;
? ?Quality quality;
? ?int length;
? ?int width;
? ?int height;
}

public class FishTankWithVolumeDto {
? ?FishDto fish;
? ?MaterialDto material;
? ?QualityDto quality;
? ?VolumeDto volume;
}

public class VolumeDto {
? ?int volume;
? ?String description;
}

@Mapper
public abstract class FishTankMapperWithVolume {

? ?@Mapping(target = "fish.kind", source = "source.fish.type")
? ?@Mapping(target = "material.materialType", source = "source.material")
? ?@Mapping(target = "quality.document", source = "source.quality.report")
? ?@Mapping(target = "volume", source = "source")
? ?abstract FishTankWithVolumeDto map(FishTank source);

? ?VolumeDto mapVolume(FishTank source) {
? ? ? ?int volume = source.length * source.width * source.height;
? ? ? ?String desc = volume < 100 ? "Small" : "Large";
? ? ? ?return new VolumeDto(volume, desc);
? ?}
}

注意@Mapping注釋,其中source字段等于"source",表示方法映射(FishTank源)中的參數(shù)名稱source本身,而不是FishTank中的(目標(biāo))屬性。

5.5調(diào)用其他映射器

除了在相同的映射器類型上定義的方法之外,MapStruct還可以調(diào)用在其他類中定義的映射方法,無論是由MapStruct生成的映射器還是手寫的映射方法。

例如,Car類可能包含一個屬性manufacturingDate,而對應(yīng)的DTO屬性是String類型。為了映射這個屬性,你可以像這樣實現(xiàn)一個映射器類:

public class DateMapper {

? ?public String asString(Date date) {
? ? ? ?return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
? ? ? ? ? ?.format( date ) : null;
? ?}

? ?public Date asDate(String date) {
? ? ? ?try {
? ? ? ? ? ?return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
? ? ? ? ? ? ? ?.parse( date ) : null;
? ? ? ?}
? ? ? ?catch ( ParseException e ) {
? ? ? ? ? ?throw new RuntimeException( e );
? ? ? ?}
? ?}
}

在CarMapper接口的@Mapper注釋中,像這樣引用DateMapper類:

@Mapper(uses=DateMapper.class)
public interface CarMapper {

? ?CarDto carToCarDto(Car car);
}

在為carToCarDto()方法的實現(xiàn)生成代碼時,MapStruct將查找一個將Date對象映射到String的方法,在DateMapper類中找到它,并生成一個調(diào)用asString()來映射manufacturingDate屬性。

生成的映射器使用為它們配置的組件模型檢索引用的映射器。例如,如果CDI被用作CarMapper的組件模型,DateMapper也必須是CDI bean。當(dāng)使用默認(rèn)組件模型時,任何要被MapStruct生成的映射器引用的手寫映射器類必須聲明一個公共的無參數(shù)構(gòu)造函數(shù)才能被實例化。

5.6基于限定符的映射方法選擇

在許多情況下,需要映射具有不同行為的具有相同方法簽名(除了名稱)的方法。MapStruct有一個方便的機(jī)制來處理這種情況:@Qualifier (org.mapstruct.Qualifier)?!皅ualifier”是用戶可以編寫的自定義注釋,“粘貼”到映射方法上,該映射方法作為使用的映射器包含,可以在bean屬性映射中引用。多個限定符可以“粘在”一個方法和映射上。

下面舉一個例子,存在將String title翻譯為英語或者德語兩種映射方法。

public class Titles {

? ?public String translateTitleEG(String title) {
? ? ? ?// some mapping logic
? ?}

? ?public String translateTitleGE(String title) {
? ? ? ?// some mapping logic
? ?}
}

還有一個使用這個手寫映射器的映射器,其中源和目標(biāo)有一個屬性“title”,應(yīng)該被映射:

@Mapper( uses = Titles.class )
public interface MovieMapper {

? ? GermanRelease toGerman( OriginalRelease movies );

}

如果不使用限定符,這將導(dǎo)致模棱兩可的映射方法錯誤,因為找到了2個限定方法(translateTitleEG, translateTitleGE),而MapStruct將沒有提示選擇哪一個。

輸入限定符方法:

import org.mapstruct.Qualifier;

@Qualifier
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface TitleTranslator {
}

并且,一些限定符指示使用哪個翻譯器將源語言映射到目標(biāo)語言:

import org.mapstruct.Qualifier;

@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface EnglishToGerman {
}import org.mapstruct.Qualifier;

@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface GermanToEnglish {
}

請注意目標(biāo)TitleTranslator在類型級別,英語到德語,德語到英語的方法級別!

然后,使用限定符,映射看起來像這樣:

@Mapper( uses = Titles.class )
public interface MovieMapper {

? ? @Mapping( target = "title", qualifiedBy = { TitleTranslator.class, EnglishToGerman.class } )
? ? GermanRelease toGerman( OriginalRelease movies );

}@TitleTranslator
public class Titles {

? ?@EnglishToGerman
? ?public String translateTitleEG(String title) {
? ? ? ?// some mapping logic
? ?}

? ?@GermanToEnglish
? ?public String translateTitleGE(String title) {
? ? ? ?// some mapping logic
? ?}
}

在許多情況下,聲明一個新的注釋來幫助選擇過程可能會超出您的預(yù)期。對于這些情況,MapStruct有@Named注釋。這個注釋是一個預(yù)定義的限定符(帶有@Qualifier本身的注釋),可以用來命名一個Mapper,或者更直接地通過它的值命名一個映射方法。上面的相同示例看起來像:

@Named("TitleTranslator")
public class Titles {

? ?@Named("EnglishToGerman")
? ?public String translateTitleEG(String title) {
? ? ? ?// some mapping logic
? ?}

? ?@Named("GermanToEnglish")
? ?public String translateTitleGE(String title) {
? ? ? ?// some mapping logic
? ?}
}@Mapper( uses = Titles.class )
public interface MovieMapper {

? ? @Mapping( target = "title", qualifiedByName = { "TitleTranslator", "EnglishToGerman" } )
? ? GermanRelease toGerman( OriginalRelease movies );

}

6集合映射

集合類型(List、Set等)的映射與映射bean類型的方式相同,即通過在映射器接口中定義具有所需源和目標(biāo)類型的映射方法。MapStruct支持來自Java集合框架的大量可迭代類型。

生成的代碼將包含一個循環(huán),該循環(huán)遍歷源集合,轉(zhuǎn)換每個元素并將其放入目標(biāo)集合。如果在給定的映射器或它使用的映射器中找到集合元素類型的映射方法,則調(diào)用該方法來執(zhí)行元素轉(zhuǎn)換。或者,如果存在源元素類型和目標(biāo)元素類型的隱式轉(zhuǎn)換,則將調(diào)用此轉(zhuǎn)換例程。示例如下:

@Mapper
public interface CarMapper {

? ?Set<String> integerSetToStringSet(Set<Integer> integers);

? ?List<CarDto> carsToCarDtos(List<Car> cars);

? ?CarDto carToCarDto(Car car);
}

生成的integerSetToStringSet實現(xiàn)為每個元素執(zhí)行從Integer到String的轉(zhuǎn)換,而生成的carsToCarDtos()方法為每個包含的元素調(diào)用了carToCarDto()方法,如下所示:

//GENERATED CODE
@Override
public Set<String> integerSetToStringSet(Set<Integer> integers) {
? ?if ( integers == null ) {
? ? ? ?return null;
? ?}

? ?Set<String> set = new LinkedHashSet<String>();

? ?for ( Integer integer : integers ) {
? ? ? ?set.add( String.valueOf( integer ) );
? ?}

? ?return set;
}

@Override
public List<CarDto> carsToCarDtos(List<Car> cars) {
? ?if ( cars == null ) {
? ? ? ?return null;
? ?}

? ?List<CarDto> list = new ArrayList<CarDto>();

? ?for ( Car car : cars ) {
? ? ? ?list.add( carToCarDto( car ) );
? ?}

? ?return list;
}

注意,當(dāng)映射bean的集合類型屬性時,MapStruct將尋找具有匹配參數(shù)和返回類型的集合映射方法,例如從Car#passengers(類型為List<Person>)到CarDto#passengers(類型為List<PersonDto>)。

//GENERATED CODE
carDto.setPassengers( personsToPersonDtos( car.getPassengers() ) );
...

一些框架和庫只公開JavaBeans的getter,而沒有集合類型屬性的setter。使用JAXB從XML模式生成的類型默認(rèn)情況下遵循此模式。在這種情況下,生成的用于映射這樣一個屬性的代碼調(diào)用它的getter并添加所有映射的元素:

//GENERATED CODE
carDto.getPassengers().addAll( personsToPersonDtos( car.getPassengers() ) );
...

6.1Map映射

public interface SourceTargetMapper {

? ?@MapMapping(valueDateFormat = "dd.MM.yyyy")
? ?Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}

與可迭代映射類似,生成的代碼將遍歷源映射,轉(zhuǎn)換每個值和鍵(通過隱式轉(zhuǎn)換或調(diào)用另一個映射方法),并將它們放入目標(biāo)映射:

//GENERATED CODE
@Override
public Map<Long, Date> stringStringMapToLongDateMap(Map<String, String> source) {
? ?if ( source == null ) {
? ? ? ?return null;
? ?}

? ?Map<Long, Date> map = new LinkedHashMap<Long, Date>();

? ?for ( Map.Entry<String, String> entry : source.entrySet() ) {

? ? ? ?Long key = Long.parseLong( entry.getKey() );
? ? ? ?Date value;
? ? ? ?try {
? ? ? ? ? ?value = new SimpleDateFormat( "dd.MM.yyyy" ).parse( entry.getValue() );
? ? ? ?}
? ? ? ?catch( ParseException e ) {
? ? ? ? ? ?throw new RuntimeException( e );
? ? ? ?}

? ? ? ?map.put( key, value );
? ?}

? ?return map;
}

6.2用于收集映射的實現(xiàn)類型

當(dāng)可迭代或map映射方法聲明接口類型為返回類型時,它的一個實現(xiàn)類型將在生成的代碼中實例化。下表顯示了支持的接口類型及其相應(yīng)的實現(xiàn)類型,如在生成的代碼中實例化:

Interface typeImplementation typeIterableArrayListCollectionArrayListListArrayListSetLinkedHashSetSortedSetTreeSetNavigableSetTreeSetMapLinkedHashMapSortedMapTreeMapNavigableMapTreeMapConcurrentMapConcurrentHashMapConcurrentNavigableMapConcurrentSkipListMap

7.枚舉映射

感覺用處不大。

8.高級映射選項

8.1默認(rèn)值和常量

可以指定默認(rèn)值,以便在相應(yīng)的源屬性為空時將預(yù)定義值設(shè)置為目標(biāo)屬性??梢灾付ǔA恳栽谌魏吻闆r下設(shè)置這樣的預(yù)定義值。默認(rèn)值和常量被指定為String值。當(dāng)目標(biāo)類型為原語類型或盒裝類型時,String值為文字值。在這種情況下,只要它們是有效的文字,就允許使用位/八進(jìn)制/十進(jìn)制/十六進(jìn)制模式。在所有其他情況下,常量或默認(rèn)值都要通過內(nèi)置轉(zhuǎn)換或調(diào)用其他映射方法進(jìn)行類型轉(zhuǎn)換,以匹配目標(biāo)屬性所需的類型。

帶有常量的映射不能包含對源屬性的引用。下面的例子展示了一些使用默認(rèn)值和常量的映射:

@Mapper(uses = StringListMapper.class)
public interface SourceTargetMapper {

? ?SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );

? ?@Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")
? ?@Mapping(target = "longProperty", source = "longProp", defaultValue = "-1")
? ?@Mapping(target = "stringConstant", constant = "Constant Value")
? ?@Mapping(target = "integerConstant", constant = "14")
? ?@Mapping(target = "longWrapperConstant", constant = "3001")
? ?@Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014")
? ?@Mapping(target = "stringListConstants", constant = "jack-jill-tom")
? ?Target sourceToTarget(Source s);
}

如果s.getStringProp() == null,則目標(biāo)屬性stringProperty將被設(shè)置為"undefined",而不是應(yīng)用s.getStringProp()中的值。如果s.getLongProperty() == null,則目標(biāo)屬性longProperty將被設(shè)置為-1。字符串“Constant Value”按原樣設(shè)置為目標(biāo)屬性stringConstant。值“3001”被類型轉(zhuǎn)換為目標(biāo)屬性longWrapperConstant的Long(包裝器)類。日期屬性還需要日期格式。常量“jack- gill -tom”演示了如何調(diào)用手工編寫的類StringListMapper,將虛線分隔的列表映射到list <String>。

8.2表達(dá)式

通過表達(dá)式,可以包含來自多種語言的結(jié)構(gòu)。

目前只支持Java作為一種語言。這個特性在調(diào)用構(gòu)造函數(shù)時非常有用。整個源對象可在表達(dá)式中使用。應(yīng)該注意只插入有效的Java代碼:MapStruct不會在生成時驗證表達(dá)式,但是在編譯期間生成的類中會顯示錯誤。

下面的例子演示了如何將兩個源屬性映射到一個目標(biāo):

@Mapper
public interface SourceTargetMapper {

? ?SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );

? ?@Mapping(target = "timeAndFormat",
? ? ? ? expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )")
? ?Target sourceToTarget(Source s);
}

該示例演示了如何將源屬性time和format組合成一個目標(biāo)屬性TimeAndFormat。請注意,指定完全限定包名是因為MapStruct不負(fù)責(zé)TimeAndFormat類的導(dǎo)入(除非在SourceTargetMapper中顯式地使用它)。這可以通過在@Mapper注釋上定義導(dǎo)入來解決。

imports org.sample.TimeAndFormat;

@Mapper( imports = TimeAndFormat.class )
public interface SourceTargetMapper {

? ?SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );

? ?@Mapping(target = "timeAndFormat",
? ? ? ? expression = "java( new TimeAndFormat( s.getTime(), s.getFormat() ) )")
? ?Target sourceToTarget(Source s);
}

8.3默認(rèn)表達(dá)式

默認(rèn)表達(dá)式是默認(rèn)值和表達(dá)式的組合。只有當(dāng)源屬性為空時才會使用它們。

適用于表達(dá)式的默認(rèn)表達(dá)式也適用相同的警告和限制。只支持Java,并且MapStruct不會在生成時驗證表達(dá)式。

下面的例子演示了在source屬性不存在時如何使用默認(rèn)表達(dá)式來設(shè)置值(例如is null):

imports java.util.UUID;

@Mapper( imports = UUID.class )
public interface SourceTargetMapper {

? ?SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );

? ?@Mapping(target="id", source="sourceId", defaultExpression = "java( UUID.randomUUID().toString() )")
? ?Target sourceToTarget(Source s);
}

這個例子演示了如何使用defaultExpression來設(shè)置一個ID字段,如果源字段是空的,它可以用來從源對象中獲取現(xiàn)有的sourceId,如果它是空的,或者創(chuàng)建一個新的ID。請注意,指定完全限定包名是因為MapStruct不負(fù)責(zé)UUID類的導(dǎo)入(除非在SourceTargetMapper中顯式地使用它)。這可以通過在@Mapper注釋上定義導(dǎo)入來解決。

8.4子類映射

當(dāng)輸入類型和結(jié)果類型都具有繼承關(guān)系時,您可能希望將正確的實例化映射到匹配的實例化。假設(shè)蘋果和香蕉都是水果的實例化。

@Mapper
public interface FruitMapper {

? ?@SubclassMapping( source = AppleDto.class, target = Apple.class )
? ?@SubclassMapping( source = BananaDto.class, target = Banana.class )
? ?Fruit map( FruitDto source );

}

如果只使用普通映射,AppleDto和BananaDto都將被制作成一個Fruit對象,而不是一個Apple和一個Banana對象。通過使用子類映射,appldtotoapple映射將用于AppleDto對象,而BananaDtoToBanana映射將用于BananaDto對象。如果你嘗試映射一個葡萄到它仍然會把它變成一個水果。

如果Fruit是一個抽象類或接口,則會得到編譯錯誤。

為了允許抽象類或接口的映射,你需要將subclassExhaustiveStrategy設(shè)置為RUNTIME_EXCEPTION,你可以在@MapperConfig, @Mapper或@BeanMapping注釋中這樣做。如果你將一個GrapeDto傳遞給一個IllegalArgumentException將被拋出,因為它不知道如何映射一個GrapeDto。為它添加缺失的(@SubclassMapping)將修復(fù)這個問題。

8.5確定結(jié)果類型

當(dāng)結(jié)果類型具有繼承關(guān)系時,選擇映射方法(@Mapping)或工廠方法(@BeanMapping)可能會產(chǎn)生歧義。假設(shè)蘋果和香蕉都是水果的實例化。

@Mapper( uses = FruitFactory.class )
public interface FruitMapper {

? ?@BeanMapping( resultType = Apple.class )
? ?Fruit map( FruitDto source );

}public class FruitFactory {

? ?public Apple createApple() {
? ? ? ?return new Apple( "Apple" );
? ?}

? ?public Banana createBanana() {
? ? ? ?return new Banana( "Banana" );
? ?}
}

那么,哪些水果必須在映射方法Fruit map(FruitDto source)中因式分解呢?香蕉還是蘋果?這里是@BeanMapping#resultType派上用場的地方。它控制要選擇的工廠方法,或者在沒有工廠方法的情況下控制要創(chuàng)建的返回類型。

8.6控制 'null' 參數(shù)的映射結(jié)果

當(dāng)映射方法的源參數(shù)等于null時,MapStruct提供了對要創(chuàng)建的對象的控制。默認(rèn)情況下將返回null。

但是,通過指定nullValueMappingStrategy = nullValueMappingStrategy。@BeanMapping, @IterableMapping, @MapMapping上的RETURN_DEFAULT,或者全局上的@Mapper或@MapperConfig,映射結(jié)果可以被修改為返回空的默認(rèn)值。這意味著:

  • Bean映射:將返回一個'空'的目標(biāo)Bean,除了常量和表達(dá)式,它們將在出現(xiàn)時被填充。

  • Iterables / Arrays:將返回一個空的iterable。

  • Maps:將返回一個空的map。

這種策略以分層的方式起作用。在映射方法級別設(shè)置nullValueMappingStrategy將覆蓋@Mapper#nullValueMappingStrategy, @Mapper#nullValueMappingStrategy將覆蓋@MapperConfig#nullValueMappingStrategy。

8.7控制 'null' 集合或映射參數(shù)的映射結(jié)果

通過控制'null'參數(shù)的映射結(jié)果,可以控制當(dāng)映射方法的源參數(shù)為null時應(yīng)該如何構(gòu)造返回類型。這適用于所有映射方法(bean、iterable或map映射方法)。

然而,MapStruct還提供了一種更專用的方式來控制集合/映射應(yīng)該如何映射。例如,返回默認(rèn)的(空的)集合/映射,但為bean返回null。

對于集合(可迭代對象),可以通過以下方式控制:

  • MapperConfig#nullValueIterableMappingStrategy

  • Mapper#nullValueIterableMappingStrategy

  • IterableMapping#nullValueMappingStrategy

對于Map,這可以通過以下方式進(jìn)行控制:

  • MapperConfig#nullValueMapMappingStrategy

  • Mapper#nullValueMapMappingStrategy

  • MapMapping#nullValueMappingStrategy

8.8條件映射

條件映射是源狀態(tài)檢查的一種類型。不同之處在于,它允許用戶編寫自定義條件方法,這些方法將被調(diào)用來檢查是否需要映射屬性。

自定義條件方法是一個帶有org.mapstruct.Condition注釋并返回布爾值的方法。

例如,如果你只想映射一個字符串屬性,當(dāng)它不是' null ',它不是空的,那么你可以這樣做:

@Mapper
public interface CarMapper {

? ?CarDto carToCarDto(Car car);

? ?@Condition
? ?default boolean isNotEmpty(String value) {
? ? ? ?return value != null && !value.isEmpty();
? ?}
}

生成的映射器如下所示:

// GENERATED CODE
public class CarMapperImpl implements CarMapper {

? ?@Override
? ?public CarDto carToCarDto(Car car) {
? ? ? ?if ( car == null ) {
? ? ? ? ? ?return null;
? ? ? ?}

? ? ? ?CarDto carDto = new CarDto();

? ? ? ?if ( isNotEmpty( car.getOwner() ) ) {
? ? ? ? ? ?carDto.setOwner( car.getOwner() );
? ? ? ?}

? ? ? ?// Mapping of other properties

? ? ? ?return carDto;
? ?}
}

如果有一個自定義的@Condition方法適用于該屬性,那么它將優(yōu)先于bean本身的狀態(tài)檢查方法。

除了source屬性的值之外,用@Condition注釋的方法也可以將source參數(shù)作為輸入。

9重用映射配置

9.1逆映射

在雙向映射的情況下,例如從實體到DTO和從DTO到實體,正向方法和反向方法的映射規(guī)則通常是相似的,可以簡單地通過交換源和目標(biāo)來反轉(zhuǎn)。

使用@InheritInverseConfiguration注釋來指示一個方法應(yīng)該繼承對應(yīng)的反向方法的反向配置。

在下面的示例中,不需要手動編寫逆映射??紤]一個有幾個映射的情況,因此編寫逆映射可能很麻煩,而且容易出錯。

@Mapper
public interface CarMapper {

? ?@Mapping(target = "seatCount", source = "numberOfSeats")
? ?CarDto carToDto(Car car);

? ?@InheritInverseConfiguration
? ?@Mapping(target = "numberOfSeats", ignore = true)
? ?Car carDtoToCar(CarDto carDto);
}

9.2共享配置

MapStruct提供了通過指向帶有@MapperConfig注釋的中心接口來定義共享配置的可能性。要讓映射器使用共享配置,需要在@Mapper#config屬性中定義配置接口。

@MapperConfig注釋具有與@Mapper注釋相同的屬性。沒有通過@Mapper給出的任何屬性都將從共享配置中繼承。在@Mapper中指定的屬性優(yōu)先于通過引用的配置類指定的屬性。列表屬性(如uses)被簡單地組合起來:

@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注釋的接口也可以聲明映射方法的原型,這些方法可以用來繼承方法級映射注釋。這樣的原型方法不打算作為映射器API的一部分來實現(xiàn)或使用。

@MapperConfig(
? ?uses = CustomMapperViaMapperConfig.class,
? ?unmappedTargetPolicy = ReportingPolicy.ERROR,
? ?mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG
)
public interface CentralConfig {

? ?// 不打算生成,但要攜帶可繼承的映射注釋:
? ?@Mapping(target = "primaryKey", source = "technicalKey")
? ?BaseEntity anyDtoToEntity(BaseDto dto);
}@Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } )
public interface SourceTargetMapper {

? ?@Mapping(target = "numberOfSeats", source = "seatCount")
? ?// 額外繼承自CentralConfig,因為Car擴(kuò)展了BaseEntity, CarDto擴(kuò)展了BaseDto:
? ?// @Mapping(target = "primaryKey", source = "technicalKey")
? ?Car toCar(CarDto car)
}

10自定義映射

有時需要在某些映射方法之前或之后應(yīng)用自定義邏輯。MapStruct為此提供了兩種方法:decorator允許對特定映射方法進(jìn)行類型安全的自定義,而before-mapping和after-mapping生命周期方法允許對給定源或目標(biāo)類型的映射方法進(jìn)行通用的自定義。

10.1使用裝飾器進(jìn)行映射定制

在某些情況下,可能需要自定義生成的映射方法,例如,在目標(biāo)對象中設(shè)置一個不能由生成的方法實現(xiàn)設(shè)置的附加屬性。MapStruct使用裝飾器支持這個需求。

要將裝飾器應(yīng)用到映射器類,請使用@DecoratedWith注釋指定它。

@Mapper
@DecoratedWith(PersonMapperDecorator.class)
public interface PersonMapper {

? ?PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class );

? ?PersonDto personToPersonDto(Person person);

? ?AddressDto addressToAddressDto(Address address);
}

裝飾器必須是被裝飾映射器類型的子類型。你可以使它成為一個抽象類,它只允許實現(xiàn)那些你想自定義的映射器接口的方法。對于所有未實現(xiàn)的方法,將使用默認(rèn)生成例程生成對原始映射器的簡單委托。

下面顯示的PersonMapperDecorator自定義了personToPersonDto()。它設(shè)置了一個在映射的源類型中不存在的附加屬性。addressToAddressDto()方法不是自定義的。

public abstract class PersonMapperDecorator implements PersonMapper {

? ?private final PersonMapper delegate;

? ?public PersonMapperDecorator(PersonMapper delegate) {
? ? ? ?this.delegate = delegate;
? ?}

? ?@Override
? ?public PersonDto personToPersonDto(Person person) {
? ? ? ?PersonDto dto = delegate.personToPersonDto( person );
? ? ? ?dto.setFullName( person.getFirstName() + " " + person.getLastName() );
? ? ? ?return dto;
? ?}
}

該示例展示了如何選擇性地使用生成的默認(rèn)實現(xiàn)注入委托,并在自定義裝飾器方法中使用此委托。

對于componentModel = "default"的映射器,定義一個帶單個參數(shù)的構(gòu)造函數(shù),該參數(shù)接受被修飾的映射器的類型。

當(dāng)使用組件模型spring或jsr330時,這需要以不同的方式處理。

10.1.1使用Spring組件模型的裝飾器

當(dāng)在具有組件模型spring的映射器上使用@DecoratedWith時,生成的原始映射器的實現(xiàn)將使用spring注釋@Qualifier(“delegate”)進(jìn)行注釋。要在你的裝飾器中自動裝配那個bean,也要添加那個限定符注釋:

public abstract class PersonMapperDecorator implements PersonMapper {

? ? @Autowired
? ? @Qualifier("delegate")
? ? private PersonMapper delegate;

? ? @Override
? ? public PersonDto personToPersonDto(Person person) {
? ? ? ? PersonDto dto = delegate.personToPersonDto( person );
? ? ? ? dto.setName( person.getFirstName() + " " + person.getLastName() );

? ? ? ? return dto;
? ? }
}

生成的擴(kuò)展裝飾器的類使用Spring的@Primary注釋。要在應(yīng)用程序中自動配置裝飾映射器,不需要做任何特別的事情:

@Autowired
private PersonMapper personMapper; // injects the decorator, with the injected original mapper

10.1.2使用JSR 330組件模型的裝飾器

JSR 330沒有指定限定符,只允許指定bean的名稱。因此,原始mapper的生成實現(xiàn)用@Named("full -qualified-name-of-generated-implementation")注釋(請注意,當(dāng)使用裝飾器時,mapper實現(xiàn)的類名以下劃線結(jié)尾)。要將該bean注入到你的裝飾器中,請在delegate字段中添加相同的注釋:

public abstract class PersonMapperDecorator implements PersonMapper {

? ?@Inject
? ?@Named("org.examples.PersonMapperImpl_")
? ?private PersonMapper delegate;

? ?@Override
? ?public PersonDto personToPersonDto(Person person) {
? ? ? ?PersonDto dto = delegate.personToPersonDto( person );
? ? ? ?dto.setName( person.getFirstName() + " " + person.getLastName() );

? ? ? ?return dto;
? ?}
}

與其他組件模型不同,使用站點必須知道一個映射器是否被裝飾過,對于裝飾過的映射器,必須添加無參數(shù)的@Named注釋來選擇要注入的裝飾器:

@Inject
@Named
private PersonMapper personMapper; // injects the decorator, with the injected original mapper

10.2使用before-mapping和after-mapping方法的映射自定義

當(dāng)涉及到定制映射器時,裝飾器可能并不總是適合需要。例如,如果您不僅需要為幾個選定的方法執(zhí)行定制,而且需要為映射特定超類型的所有方法執(zhí)行定制:在這種情況下,您可以使用在映射開始之前或映射完成之后調(diào)用的回調(diào)方法。

回調(diào)方法可以在抽象映射器本身中實現(xiàn),也可以在mapper #uses中的類型引用中實現(xiàn),或者在作為@Context參數(shù)使用的類型中實現(xiàn)。

@Mapper
public abstract class VehicleMapper {

? ?@BeforeMapping
? ?protected void flushEntity(AbstractVehicle vehicle) {
? ? ? ?// I would call my entity manager's flush() method here to make sure my entity
? ? ? ?// is populated with the right @Version before I let it map into the DTO
? ?}

? ?@AfterMapping
? ?protected void fillTank(AbstractVehicle vehicle, @MappingTarget AbstractVehicleDto result) {
? ? ? ?result.fuelUp( new Fuel( vehicle.getTankCapacity(), vehicle.getFuelType() ) );
? ?}

? ?public abstract CarDto toCarDto(Car car);
}

// Generates something like this:
public class VehicleMapperImpl extends VehicleMapper {

? ?public CarDto toCarDto(Car car) {
? ? ? ?flushEntity( car );

? ? ? ?if ( car == null ) {
? ? ? ? ? ?return null;
? ? ? ?}

? ? ? ?CarDto carDto = new CarDto();
? ? ? ?// attributes mapping ...

? ? ? ?fillTank( car, carDto );

? ? ? ?return carDto;
? ?}
}

如果@BeforeMapping / @AfterMapping方法有參數(shù),只有當(dāng)方法的返回類型(如果非void)可賦值給映射方法的返回類型,并且所有參數(shù)都可以由映射方法的源參數(shù)或目標(biāo)參數(shù)賦值時,才會生成方法調(diào)用:

  • 帶@MappingTarget注釋的參數(shù)用映射的目標(biāo)實例填充。

  • 帶@TargetType注釋的參數(shù)用映射的目標(biāo)類型填充。

  • 帶@Context注釋的參數(shù)用映射方法的上下文參數(shù)填充。

  • 任何其他參數(shù)都使用映射的源參數(shù)填充。

  • 對于非空方法,如果方法調(diào)用的返回值不為空,則作為映射方法的結(jié)果返回。

與映射方法一樣,可以為映射前/映射后方法指定類型參數(shù)。

@Mapper
public abstract class VehicleMapper {

? ?@PersistenceContext
? ?private EntityManager entityManager;

? ?@AfterMapping
? ?protected <T> T attachEntity(@MappingTarget T entity) {
? ? ? ?return entityManager.merge(entity);
? ?}

? ?public abstract CarDto toCarDto(Car car);
}

// Generates something like this:
public class VehicleMapperImpl extends VehicleMapper {

? ?public CarDto toCarDto(Car car) {
? ? ? ?if ( car == null ) {
? ? ? ? ? ?return null;
? ? ? ?}

? ? ? ?CarDto carDto = new CarDto();
? ? ? ?// attributes mapping ...

? ? ? ?CarDto target = attachEntity( carDto );
? ? ? ?if ( target != null ) {
? ? ? ? ? ?return target;
? ? ? ?}

? ? ? ?return carDto;
? ?}
}

所有可以應(yīng)用于映射方法的映射前/映射后方法都將被使用??梢允褂没谙薅ǚ挠成浞椒ㄟx擇來進(jìn)一步控制可以選擇哪些方法,不可以選擇哪些方法。為此,限定符注釋需要應(yīng)用于before/after方法,并在BeanMapping#qualifiedBy或IterableMapping#qualifiedBy中引用。

方法調(diào)用的順序主要由它們的變體決定:

沒有@MappingTarget參數(shù)的@BeforeMapping方法在對源參數(shù)進(jìn)行空檢查和構(gòu)造新的目標(biāo)bean之前被調(diào)用。

帶有@MappingTarget參數(shù)的@BeforeMapping方法在構(gòu)造新的目標(biāo)bean之后被調(diào)用。

@AfterMapping方法在映射方法的末尾在最后一個返回語句之前被調(diào)用。

在這些組中,方法調(diào)用按其定義位置排序:

  1. 在@Context參數(shù)上聲明的方法,按參數(shù)順序排序。

  2. 在映射器本身中實現(xiàn)的方法。

  3. Mapper#中引用的類型的方法按照注釋中的類型聲明的順序使用()。

  4. 在一個類型中聲明的方法在其超類型中聲明的方法之后使用。

重要提示:不能保證在一個類型中聲明的方法順序,因為它取決于編譯器和處理環(huán)境實現(xiàn)。當(dāng)使用構(gòu)建器時,@AfterMapping注釋方法必須將構(gòu)建器作為@MappingTarget注釋參數(shù),以便該方法能夠修改將要構(gòu)建的對象。當(dāng)@AfterMapping注釋的方法范圍完成時,將調(diào)用構(gòu)建方法。如果真正的目標(biāo)被用作@MappingTarget注釋參數(shù),MapStruct將不會調(diào)用@AfterMapping注釋方法。


一文讀懂MapStruct 1.5.5.Final的評論 (共 條)

分享到微博請遵守國家法律
田林县| 绵阳市| 奉贤区| 莱西市| 改则县| 清涧县| 来凤县| 阿巴嘎旗| 桃江县| 法库县| 金华市| 吉水县| 高密市| 天柱县| 南平市| 阆中市| 安乡县| 册亨县| 兴仁县| 南通市| 白玉县| 太仓市| 图片| 河津市| 崇阳县| 靖安县| 黔江区| 维西| 乐安县| 肃宁县| 河津市| 两当县| 宽甸| 卢湾区| 来宾市| 油尖旺区| 鄄城县| 华容县| 永德县| 星座| 武乡县|