-
MapStruct GitHub 訪問地址 : https://github.com/mapstruct/mapstruct/
-
MapStrcut與其它工具對比以及使用說明! http://www.tuicool.com/articles/uiIRjai
-
是否一直在使用BeanUtils.copyProperties 用於對象屬性拷貝。 出現種種小問題。
-
- 會將同名屬性拷貝到另外一個對象中,操作方便但是存在一個缺陷 (速度慢)
-
- 有些同名字段卻無法進行特殊化處理,將會導致不想修改的字段被覆蓋。也不能自定義屬性映射
- 在 mvc層 我們經常會DTO對象返回給前端 進行字段渲染。我們不喜歡將所有字段都顯示給前端,或者我們需要修改字段返回給前端,例如 數據中存儲的上架下架是0,1 但是前端需要的字段是true 和 false。 我們都得進行手動判斷處理然后編輯成DTO返回給前端
MapStruct
是一種類型安全的bean
映射類生成java注釋處理器
。
我們要做的就是定義一個映射器接口,聲明任何必需的映射方法。在編譯的過程中,MapStruct會生成此接口的實現。該實現使用純java方法調用的源和目標對象之間的映射,MapStruct節省了時間,通過生成代碼完成繁瑣和容易出錯的代碼邏輯。。
-
MapStruct 擁有的優點:
-
- 使用普通方法調用而不是反射來快速執行,他會在編譯器生成相應的 Impl 方法調用時直接通過簡單的 getter/setter調用而不是反射或類似的方式將值從源復制到目標
-
- 編譯時類型安全性 : 只能映射彼此的對象和屬性,不能將商品實體意外映射到用戶 DTO等
-
- 在構建時清除錯誤報告,如 映射不完整 (並非所有目標屬性都被映射) 或 映射不正確(無法找到適當的映射方法或類型轉換)
-
MapStruct 提供的重要注解 :
-
- @Mapper : 標記這個接口作為一個映射接口,並且是編譯時 MapStruct 處理器的入口
-
- @Mapping : 解決源對象和目標對象中,屬性名字不同的情況
-
- Mappers.getMapper 自動生成的接口的實現可以通過 Mapper 的 class對象獲取,從而讓客戶端可以訪問 Mapper接口的實現
<?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);
}
- 自定義方法添加到映射器 : 在某些情況下,需要手動實現 MapStruct 無法生成的從一種類型到另一種類型的特定映射,有如下兩種實現方法 :
- 方法1> 在另一個類上實現此類方法,然后由 MapStruct 生成的映射器使用該方法
- 方法2> 在Java 8或更高版本時,可以直接在映射器界面中實現自定義方法作為默認方法。如果參數和返回類型匹配,生成的代碼將調用默認方法
@Mapper
public interface CarMapper {
CarMapper MAPPER = Mappers.getMapper(CarMapper.class);
@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);
}
- 直接字段訪問映射 : 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的轉換
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)
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");
}
}
-
控制 '空' 參數的映射結果 : 默認情況下 null 會返回,通過指定 nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT 上 @BeanMapping,@IterableMapping,@MapMapping,或全局上 @Mapper 或 @MappingConfig,映射結果可以被改變以返回空默認值
-
- 1> Bean映射 : 將返回一個 '空' 目標bean,除常量和表達式外,它們將在存在時填充
-
- 2> 基元 : 基元的默認值將被返回,例如 false for boolean 或 0 for int
-
- 3> Iterables/Arrays : 一個空的迭代器將被返回
-
- 4> 地圖 : 將返回空白地圖
-
共享配置 : 通過指向中心接口來定義共享配置的可能性 @MapperConfig,要使映射器使用共享配置,需要在 @Mapper#config 屬性中定義配置界面。該 @MapperConfig 注釋具有相同的屬性 @Mapper 注釋。任何未通過的屬性 @Mapper 都將從共享配置繼承。指定 @Mapper 的屬性優先於通過引用的配置類指定的屬性
@MapperConfig(uses = CustomMapperViaMapperConfig.class, unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface CentralConfig {}
@Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } )
public interface SourceTargetMapper {}
本文作者:雲楊四海
原文鏈接:對象拷貝 - 優雅的解決方案 Mapstruct
版權歸作者所有,轉載請注明出處