MapStruct 详解
GitHub 訪問地址 :?https://github.com/mapstruct/mapstruct/
使用例子 :?https://github.com/mapstruct/mapstruct-examples
?
MapStrcut與其它工具對比以及使用說明 : http://www.tuicool.com/articles/uiIRjai
?
BeanUtils.copyProperties 用于對象屬性拷貝,會將同名屬性拷貝到另外一個對象中,操作方便但是存在一個缺陷
缺陷1> 速度慢
缺陷2> 有些同名字段無法進行特殊化處理,將會導致不想修改的字段被覆蓋
?
在 mvc層 經常會遇到,是否使用 DTO(數據傳輸對象),還是直接使用 model返回。前者叫封閉領域模型風格,后者叫開放領域模型風格。因為前者可以排除一些不需要返回的字段但是需要進行拷貝,后者就不需要經過處理。另外,都會遭遇到一種情況 : 如性別在后臺是通過0和1,但是需要返回前端男或者女,可以直接使用 DTO 進行數據轉換
?
MapStruct 是用于生成類型安全,高性能和無依賴 bean映射代碼的注釋處理器,一種類型安全的 bean映射類生成 java注釋處理器,使用時只需要定義一個映射器接口來聲明任何需要的映射方法。在編譯期間,MapStruct 將生成此接口的實現。這個實現使用普通 的Java方法調用來源和目標對象之間的映射,即沒有反射或類似。與手寫映射代碼相比,MapStruct通過生成繁瑣且容易出錯的代碼來節省時間。遵循約定而不是配置方法,MapStruct使用合理的默認值,但在配置或實現特殊行為時不會采取任何步驟
?
與動態映射框架相比,MapStruct 具有以下優點 :
1>?通過使用普通方法調用而不是反射來快速執行,也就是 會在編譯器生成相應的 Impl 方法調用時直接通過簡單的 getter/setter調用而不是反射或類似的方式將值從源復制到目標
2>?編譯時類型安全性 : 只能映射彼此的對象和屬性,不能將訂單實體意外映射到客戶 DTO等
3>?在構建時清除錯誤報告,如 映射不完整 (并非所有目標屬性都被映射) 或 映射不正確(無法找到適當的映射方法或類型轉換)
?
在進行數據轉換時,如果源和目標實體中映射屬性的類型不同,則 MapStruct將應用自動轉換,或者可選地調用/創建另一個映射方法 (如 : 對于驅動程序/引擎屬性)。當且僅當源和目標屬性是 Bean屬性 并且它們本身是 Bean或簡單屬性時,MapStruct 才會創建新的映射方法。即他們不是 Collection 或 Map 鍵入屬性。通過創建包含源屬性中元素的目標集合類型的新實例,將復制具有相同元素類型的集合類型屬性。對于具有不同元素類型的集合類型的屬性,每個元素將被單獨映射并添加到目標集合中
?
MapStruct 處理器選項
選項
目的
默認
mapstruct.suppressGeneratorTimestamp
如果設置為 true ,@Generated 則會在生成的映射器類的注釋中創建時間戳
false
mapstruct.suppressGeneratorVersionInfoComment
如果設置為 true ,則會在生成的映射器類中注釋中的 comment 屬性創建 @Generated 被抑制。該注釋包含有關 MapStruct版本和用于注釋處理的編譯器的信息
false
mapstruct.defaultComponentModel
根據生成映射器的組件模型的名稱,支持的值 :
1>?default : 映射器不使用組件模型,通常通過實例檢索實例 Mappers#getMapper(Class)
2>?cdi : 生成的映射器是一個應用程序范圍的CDI bean,可以通過檢索 @Inject
3>?spring : 生成的映射器是一個單一范圍的 Spring bean,可以通過檢索 @Autowired
4>?jsr330 : 生成的映射器使用 {@code @Named} 進行注釋,并且可以通過 @Inject 使用 Spring 進行檢索
如果為特定的映射器通過組件模型 @Mapper#componentModel() ,則注釋中的值優先
default
mapstruct.unmappedTargetPolicy
在映射方法的目標對象的屬性未填充源值的情況下應用的默認報告策略,支持的值 :
1> ERROR : 任何未映射的目標屬性都將導致映射代碼生成失敗
2> WARN : 任何未映射的目標屬性將在構建時引發警告
3> IGNORE : 未映射的目標屬性被忽略
如果為特定的映射器通過了策略 @Mapper#unmappedTargetPolicy() ,則注釋中的值優先
WARN
MapStruct 提供的重要注解 :
@Mapper : 標記這個接口作為一個映射接口,并且是編譯時 MapStruct 處理器的入口
@Mapping : 解決源對象和目標對象中,屬性名字不同的情況
Mappers.getMapper 自動生成的接口的實現可以通過 Mapper 的 class對象獲取,從而讓客戶端可以訪問 Mapper接口的實現
?
基本使用方式 :
在 pom.xml 添加依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
?????????xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd">
????...
????<properties>
????????...
????????<org.mapstruct.version>1.2.0.Final</org.mapstruct.version>
????</properties>
?
????<dependencies>
????????...
????????<!-- MapStruct START -->
????????<dependency>
????????????<groupId>org.mapstruct</groupId>
????????????<artifactId>mapstruct-jdk8</artifactId>
????????????<version>${org.mapstruct.version}</version>
????????</dependency>
????????<dependency>
????????????<groupId>org.mapstruct</groupId>
????????????<artifactId>mapstruct-processor</artifactId>
????????????<version>${org.mapstruct.version}</version>
????????</dependency>
????????<!-- MapStruct END -->
????</dependencies>
?
????<build>
????????<plugins>
????????????<plugin>
????????????????<groupId>org.springframework.boot</groupId>
????????????????<artifactId>spring-boot-maven-plugin</artifactId>
????????????</plugin>
????????????<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>
????????????????????<compilerArgs>
????????????????????????<compilerArg>-Amapstruct.defaultComponentModel=spring</compilerArg>
????????????????????????<compilerArg>-Amapstruct.suppressGeneratorTimestamp=true</compilerArg>
????????????????????????<compilerArg>-Amapstruct.suppressGeneratorVersionInfoComment=true</compilerArg>
????????????????????</compilerArgs>
????????????????</configuration>
????????????</plugin>
????????</plugins>
????</build>
</project>
?
常用的接口工具類 :?BasicObjectMapper包含了4個基本方法,單個和集合以及反轉的單個和集合。開發中如需要對象轉換操作可直接新建 interface 并繼承 BasicObjectMapper,并在新建的接口上加上 @Mapper(componentModel = "spring"),如果是屬性中包含其它類以及該類已經存在 Mapper 則注解中加上 users = {類名.class}。componentModel = "spring" 該配置表示生成的實現類默認加上 spring @Component 注解,使用時可直接通過 @Autowire 進行注入
public interface BasicObjectMapper<SOURCE, TARGET> {
?
????@Mappings({})
????@InheritConfiguration
????TARGET to(SOURCE var1);
?
????@InheritConfiguration
????List<TARGET> to(List<SOURCE> var1);
?
????@InheritInverseConfiguration
????SOURCE from(TARGET var1);
?
????@InheritInverseConfiguration
????List<SOURCE> from(List<TARGET> var1);
?
}
?
直接使用進行對象數據轉換
@Data
public class ProductCategory {
????/** 類別編碼 */
????private String categoryCode;
????/** 類別名稱 */
????private String categoryName;
}
?
@Data
public class CategoryVo {
????private String code;
????private String name;
}
?
?
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
@Mapper
public interface CategoryMapper extends BasicObjectMapper<CategoryVo, ProductCategory> {
????CategoryMapper MAPPER = Mappers.getMapper(CategoryMapper.class);
????@Mappings({
????????????@Mapping(source = "code", target = "categoryCode"),
????????????@Mapping(source = "name", target = "categoryName")
????})
????ProductCategory to(CategoryVo source);
}
?
?
# 進行測試
public static void main(String[] args) {
????CategoryMapper categoryMapper = CategoryMapper.MAPPER;
?
????CategoryVo vo = new CategoryVo();
????vo.setCode("0000");
????vo.setName("屬性名稱");
?
????ProductCategory pc = categoryMapper.to(vo); // 通過 to方法得到 ProductCategory
????System.out.println("1" + pc);
?
????CategoryVo vo1 = categoryMapper.from(pc); // 通過 from方法得到 CategoryVo,既反轉 to方法
????System.out.println("2" + vo1);
?
????List<ProductCategory> pcList = categoryMapper.to(Arrays.asList(vo, vo1)); // 通過to方法從集合得到轉換后的集合
????System.out.println("3" + pcList);
?
????List<CategoryVo> voList = categoryMapper.from(pcList); // 反轉集合
????System.out.println("4" + voList);
}
最后使用?mvn compile?指令生成映射器的實現類
?
自定義方法添加到映射器 :?在某些情況下,需要手動實現 MapStruct 無法生成的從一種類型到另一種類型的特定映射,有如下兩種實現方法 :
方法1>?在另一個類上實現此類方法,然后由 MapStruct 生成的映射器使用該方法
方法2> 在Java 8或更高版本時,可以直接在映射器界面中實現自定義方法作為默認方法。如果參數和返回類型匹配,生成的代碼將調用默認方法
@Mapper
public interface CarMapper {
????@Mappings({...})
????CarDto carToCarDto(Car car);
?
????default PersonDto personToPersonDto(Person person) {
????????// hand-written mapping logic
????}
}
映射器也可以定義為抽象類的形式而不是接口,并直接在此映射器類中實現自定義方法。在這種情況下,MapStruct將生成抽象類的擴展,并實現所有抽象方法。這種方法優于聲明默認方法的優點是可以在映射器類中聲明附加字段
@Mapper
public abstract class CarMapper {
????@Mappings(...)
????public abstract CarDto carToCarDto(Car car);
?
????public PersonDto personToPersonDto(Person person) {
????????// hand-written mapping logic
????}
}
?
多源參數映射方法 : MapStruct 支持多個源參數的映射方法,將幾個實體組合成一個數據傳輸對象
@Mapper
public interface AddressMapper {
????@Mappings({
????????@Mapping(source = "person.description", target = "description"),
????????@Mapping(source = "address.houseNo", target = "houseNumber")
????})
????DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}
如果多個源對象定義了一個具有相同名稱的屬性,則必須使用 @Mapping 注釋來指定從中檢索屬性的源參數,如果這種歧義未得到解決,將會引發錯誤。對于在給定源對象中只存在一次的屬性,指定源參數的名稱是可選的,因為它可以自動確定
?
MapStruct 還提供直接引用源參數
@Mapper
public interface AddressMapper {
????@Mappings({
????????@Mapping(source = "person.description", target = "description"),
????????@Mapping(source = "hn", target = "houseNumber")
????})
????DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn);
}
?
更新現有?bean : 有時需要映射不創建目標類型的新實例,而是更新該類型的現有實例。這種映射可以通過為目標對象添加一個參數并用該參數標記來實現 @MappingTarget
@Mapper
public interface CarMapper {
????void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
}
還可以設置方法的返回類型為目標參數的類型,這將導致生成的實現來更新通過映射目標并返回它
?
直接字段訪問映射 :?MapStruct 支持 public 沒有 getter/setter 的字段的映射,如果 MapStruct 無法為屬性找到合適的 getter/setter方法,MapStruct 將使用這些字段作為 讀/寫訪問器。如果它是 public,則字段被認為是讀取存取器 public final。如果一個字段 static 不被視為讀取存取器只有在字段被認為是寫入訪問者的情況下 public。如果一個字段 final 和/或 static 它不被認為是寫入訪問者
public class Customer {
????private Long id;
????private String name;
????// getters and setter omitted for brevity
}
?
public class CustomerDto {
????public Long id;
????public String customerName;
}
?
@Mapper
public interface CustomerMapper {
????CustomerMapper MAPPER = Mappers.getMapper( CustomerMapper.class );
?
????@Mapping(source = "customerName", target = "name")
????Customer toCustomer(CustomerDto customerDto);
?
????@InheritInverseConfiguration
????CustomerDto fromCustomer(Customer customer);
}
生成的映射器如下
public class CustomerMapperImpl implements CustomerMapper {
????@Override
????public Customer toCustomer(CustomerDto customerDto) {
????????// ...
????????customer.setId( customerDto.id );
????????customer.setName( customerDto.customerName );
????????// ...
????}
?
????@Override
????public CustomerDto fromCustomer(Customer customer) {
????????// ...
????????customerDto.id = customer.getId();
????????customerDto.customerName = customer.getName();
????????// ...
????}
}
?
檢索映射器 :?Mapper實例 通過?org.mapstruct.factory.Mappers 的??getMapper() 方法來檢索。通常 映射器接口應該定義一個名為的成員 INSTANCE ,它包含一個映射器類型的單個實例 :
@Mapper
public interface CarMapper {
????CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
?
????CarDto carToCarDto(Car car);
}
這種模式使客戶非常容易地使用映射器對象,而無需反復實例化新的實例 :
Car car = ...;
CarDto dto = CarMapper.INSTANCE.carToCarDto( car );
?
使用依賴注入 : 通過 Spring 依賴注入可以獲取映射器對象
@Mapper(componentModel = "spring")
public interface CarMapper {
????CarDto carToCarDto(Car car);
}
?
@Inject
private CarMapper mapper;
?
數據類型轉換 :?源對象和目標對象中映射的屬性類型可能不同,MapStruct 提供自動處理類型轉換,提供如下自動轉換 :
1>?Java基本數據類型及其相應的包裝類型,如 int 和 Integer,boolean 和 Boolean 等生成的代碼是 null 轉換一個包裝型成相應的原始類型時一個感知,即 null 檢查將被執行
2>?Java基本號碼類型和包裝類型,例如之間 int 和 long 或 byte 和 Integer (大類類型數據轉換成小類可能出現精度損失)
3>?所有Java基本類型之間 (包括其包裝) 和 String 之間,例如 int 和 String 或 Boolean 和 String,java.text.DecimalFormat 均可以指定格式字符串
int 到 String的轉換
@Mapper
public interface CarMapper {
????@Mapping(source = "price", numberFormat = "$#.00")
????CarDto carToCarDto(Car car);
?
????@IterableMapping(numberFormat = "$#.00")
????List<String> prices(List<Integer> prices);
}
BigDecimal 轉換為 String
@Mapper
public interface CarMapper {
????@Mapping(source = "power", numberFormat = "#.##E0")
????CarDto carToCarDto(Car car);
}
從日期到字符串的轉換
@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);
}
?
映射對象引用 : 對象中如果包含另一個對象的引用,此時只需為引用的對象類型定義映射方法即可
@Mapper
public interface CarMapper {
????CarDto carToCarDto(Car car);
?
????PersonDto personToPersonDto(Person person);
}
?
#?映射器控制嵌套的bean映射
@Mapper
public interface FishTankMapper {
????@Mappings({
????@Mapping(target = "fish.kind", source = "fish.type"),
????@Mapping(target = "fish.name", ignore = true),
????@Mapping(target = "plant", ignore = true ),
????@Mapping(target = "ornament", ignore = true ),
????@Mapping(target = "material", 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 );
}
?
調用其他映射器 :?MapStruct 中可以調用在其他類中定義的映射方法,無論是由MapStruct生成的映射器還是手寫映射方法
#?手動實現的映射
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);
????????}
????}
}
?
#?引用另一個映射器類
@Mapper(uses = DateMapper.class)
public class CarMapper {
????CarDto carToCarDto(Car car);
}
當為該 ?carToCarDto() 方法的實現生成代碼時,MapStruct將查找將 Date 對象映射到String的方法,在 DateMapper 該類上找到它并生成 asString() 用于映射該 manufacturingDate 屬性的調用
?
映射集合 :?集合類型(映射 List,Set 等等) 以相同的方式映射 bean類型,通過定義與在映射器接口所需的源和目標類型的映射方法。生成的代碼將包含一個遍歷源集合的循環,轉換每個元素并將其放入目標集合中。如果在給定的映射器或其使用的映射器中找到了集合元素類型的映射方法,則會調用此方法以執行元素轉換。或者,如果存在源和目標元素類型的隱式轉換,則將調用此轉換例程
@Mapper
public interface CarMapper {
????Set<String> integerSetToStringSet(Set<Integer> integers);
????List<CarDto> carsToCarDtos(List<Car> cars);
????CarDto carToCarDto(Car car);
}
?
#?生成的集合映射方法
@Override
public Set<String> integerSetToStringSet(Set<Integer> integers) {
????if (integers == null) {
????????return null;
????}
????Set<String> set = new HashSet<>();
????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<>();
????for (Car car : cars) {
????????list.add(carToCarDto(car));
????}
????return list;
}
?
映射Map :
public interface SourceTargetMapper {
????@MapMapping(valueDateFormat = "dd.MM.yyyy")
????Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}
?
映射流 :
@Mapper
public interface CarMapper {
????Set<String> integerStreamToStringSet(Stream<Integer> integers);
????List<CarDto> carsToCarDtos(Stream<Car> cars);
????CarDto carToCarDto(Car car);
}
?
映射枚舉 : 默認情況下,源枚舉中的每個常量映射到目標枚舉類型中具有相同名稱的常量。如果需要,可以使用 @ValueMapping 注釋幫助將source enum中的常量映射為具有其他名稱的常量
@Mapper
public interface OrderMapper {
????OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);
????@ValueMappings({
????????????@ValueMapping(source = "EXTRA", target = "SPECIAL"),
????????????@ValueMapping(source = "STANDARD", target = "DEFAULT"),
????????????@ValueMapping(source = "NORMAL", target = "DEFAULT")
????})
????ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
}
?
默認值和常量 :?
@Mapper(uses = StringListMapper.class)
public interface SourceTargetMapper {
????SourceTargetMapper INSTANCE = Mappers.getMapper(SourceTargetMapper.class);
?
????@Mappings({
????????????@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);
}
?
表達式 :
@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);
}
?
確定結果類型 :?當結果類型具有繼承關系時,選擇映射方法(@Mapping) 或工廠方法(@BeanMapping) 可能變得不明確。假設一個Apple和一個香蕉,這兩個都是 Fruit的專業
@Mapper(uses = FruitFactory.class)
公共接口FruitMapper {
????@BeanMapping(resultType = Apple.class)
????水果圖(FruitDto來源);
}
?
公共類FruitFactory {
????public Apple createApple(){
????????返回新Apple(“Apple”);
????}
????public Banana createBanana(){
????????返回新香蕉(“香蕉”);
????}
}
?
控制'空'參數的映射結果:默認情況下null會返回,通過指定nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT上@ BeanMapping,@ IterableMapping,@ MapMapping,或全局上@Mapper或@MappingConfig,映射結果可以被改變以返回空默認值
1> Bean映射:將返回一個'空'目標bean,除常量和表達式外,它們將在存在時填充
2>基元:基元的默認值將被返回,例如假為boolean或0為int
3> Iterables / Arrays:一個空的迭代器將被返回
4>地圖:將返回空白地圖
?
共享配置:通過指向中心接口來定義共享配置的可能性@MapperConfig,要使映射器使用共享配置,需要在@ Mapper#config屬性中定義配置界面。該@MapperConfig注釋具有相同的屬性@Mapper注釋。任何未通過的屬性@Mapper都將從共享配置繼承。指定@Mapper的屬性優先于通過引用的配置類指定的屬性
@MapperConfig(uses = CustomMapperViaMapperConfig.class,unmappedTargetPolicy = ReportingPolicy.ERROR)
公共接口CentralConfig {}
?
@Mapper(config = CentralConfig.class,uses = {CustomMapperViaMapper.class})
公共接口SourceTargetMapper {}
?
---------------------?
作者:SmallPuddingHappy?
來源:CSDN?
原文:HTTPS://blog.csdn.net/chenshun123/article/details/83445438?
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
總結
以上是生活随笔為你收集整理的MapStruct 详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小米品牌
- 下一篇: BZOJ4012 [HNOI2015]开