一、痛點
程式碼中存在很多Java Bean之間的轉換,編寫對映轉化程式碼是一個繁瑣重複還易出錯的工作。使用BeanUtils工具時,對於欄位名不一致和巢狀型別不一致時,需要手動編寫。並且基於反射,對效能有一定開銷。Spring提供的BeanUtils針對apache的BeanUtils做了很多最佳化,整體效能提升了不少,不過還是使用反射實現,針對複雜場景支援能力不足。
二、MapStruct 機制
MapStruct是編譯期動態生成getter/setter,在執行期直接呼叫框架編譯好的class類實現實體對映。因此安全性高,編譯透過之後,執行期間就不會報錯。其次速度快,執行期間直接呼叫實現類,不會在執行期間使用發射進行轉換。
三、環境搭建
Maven依賴匯入:mapstruct依賴會匯入MapStruct的核心註解。由於MapStruct在編譯時工作,因此需要在<build>標籤中新增外掛maven-compiler-plugin,並在其配置中新增annotationProcessorPaths,該外掛會在構建時生成對應的程式碼。
<properties>
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
<lombok.version>1.18.12</lombok.version>
</properties>
<dependencies>
<dependency>
<groupid>org.mapstruct</groupid>
<artifactid>mapstruct</artifactid>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<version>${lombok.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>
<!--下面這個 專案中不使用 Lombok的話 不用加-->
<path>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<version>${lombok.version}</version>
</path>
</annotationprocessorpaths>
</configuration>
</plugin>
</plugins>
</build>
四、使用
單一物件轉化
建立對映:如下兩個類進行物件之間的轉換
public class Student {
private int id;
private String name;
// 兩個類中存在不同的屬性名,需要在Mapper介面中設定source和target
private String book;
// getters and setters or builder
}
public class StudentDto {
private int id;
private String name;
// 兩個類中存在不同的屬性名,需要在Mapper介面中設定source和target
private String letter;
// getters and setters or builder
}
兩者之間進行對映,需要建立一個StudentMapper
介面並使用@Mapper
註解,MapStruct
就知道這是兩個類之間的對映器。
@Mapper
public interface StudentMapper {
StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
// 兩個類中存在不同的屬性名,需要在Mapper介面中設定source和target
@Mapping(source = "student.book", target = "letter")
StudentDto toDto(Student student);
}
當我們需要將Student
屬性對映到StudentDto
時
StudentDto studentDto = StudentMapper.INSTANCE.toDto(student);
當我們構建/編譯應用程式時,MapStruct
註解處理器外掛會識別出StudentMapper
介面並生成StudentMapperImpl
實體類:如果型別中包含Builder
, MapStruct
會嘗試使用它來構建例項,如果沒有MapStruct
將透過new
關鍵字進行例項化。
public class StudentMapperImpl implements StudentMapper {
@Override
public StudentDto toDto(Student student) {
if ( student == null ) {
return null;
}
StudentDtoBuilder studentDto = StudentDto.builder();
studentDto.id(student.getId());
studentDto.name(student.getName());
// ....
return studentDto.build();
}
}
多個物件轉換為一個物件
public class Student {
private int id;
private String name;
// getters and setters or builder
}
public class StudentDto {
private int id;
private int classId;
private String name;
// getters and setters or builder
}
public class ClassInfo {
private int id;
private int classId;
private String className;
// getters and setters or builder
}
StudentMapper
介面更新如下:如果兩個屬性中包含相同的欄位時,需要透過source
和target
指定具體使用哪個類的屬性。
@Mapper
public interface StudentMapper {
StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
@Mapping(source = "student.id", target = "id")
StudentDto toDto(Student student, ClassInfo classInfo);
}
子物件對映
多數情況下,POJO
中不會只包含基本資料型別,其中往往會包含其它類。比如說,一個Student
類中包含ClassInfo
類:
public class Student {
private int id;
private String name;
private ClassInfo classInfo;
// getters and setters or builder
}
public class StudentDto {
private int id;
private String name;
private ClassInfoDto classInfoDto;
// getters and setters or builder
}
public class ClassInfo {
private int classId;
private String className;
// getters and setters or builder
}
public class ClassInfoDto {
private int classId;
private String className;
// getters and setters or builder
}
在修改StudentMapper
之前,我們先建立一個ClassInfoMapper
轉換器:
@Mapper
public interface ClassInfoMapper {
ClassInfoMapper INSTANCE = Mappers.getMapper(ClassInfoMapper.class);
ClassInfoDto dto(ClassInfo classInfo);
}
建立完ClassInfoMapper
之後,我們再修改StudentMapper
:新增uses
標識,這樣StudentMapper
就能夠使用ClassInfoMapper
對映器
@Mapper(uses = {ClassInfoMapper.class})
public interface StudentMapper {
StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
@Mapping(source="student.classInfo", target="classInfoDto")
StudentDto toDto(Student student, ClassInfo classInfo);
}
我們看先編譯後的程式碼:新增了一個新的對映方法classInfoDtoToclassInfo
。這個方法如果沒有顯示定義的情況下生成,因為我們將ClassInfoMapper
物件新增到了StudenMapper
中。
public class StudentMapperImpl implements StudentMapper {
private final ClassInfoMapper classInfoMapper = Mappers.getMapper( ClassInfoMapper.class );
@Override
public StudentDto toDto(Student student) {
if ( student == null ) {
return null;
}
StudentDtoBuilder studentDto = StudentDto.builder();
studentDto.id(student.getId());
studentDto.name(student.getName());
studentDto.classInfo = (classInfoDtoToclassInfo(student.calssInfo))
// ....
return studentDto.build();
}
protected ClassInfoDto classInfoDtoToclassInfo(ClassInfo classInfo) {
if ( classInfo == null ) {
return null;
}
ClassInfoDto classInfoDto = classInfoMapper.toDto(classInfo);
return classInfoDto;
}
}
資料型別對映
自動型別轉換適用於一下幾種情況:
【1】基本型別及其包裝類之間的轉換:int和Integer,float與Float,long與Long,boolean與Boolean等。
【2】任意基本型別與任意包裝類之間。如int和long,byte和Integer等。
【3】所有基本型別及包裝類與String之間。如boolean和String,Integer和String等。
【4】列舉和String之間。
【5】Java大數型別java.math.BigInteger, java.math.BigDecimal和Java基本型別(包括其包裝類)與String之間。
日期轉換:指定格式
public class Student {
private int id;
private String name;
private LocalDate birth;
// getters and setters or builder
}
public class StudentDto {
private int id;
private String name;
pprivate String birth;
// getters and setters or builder
}
建立對映器
@Mapper
public interface StudentMapper {
StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
// 也可以指定數字的格式
// @Mapping(source = "price", target = "price", numberFormat = "$#.00")
@Mapping(source = "birth", target = "birth", dataFormat = "dd/MM/yyyy")
StudentDto toDto(Student student, ClassInfo classInfo);
}
List對映
定義一個新的對映方法
@Mapper
public interface StudentMapper {
List<StudentDto> map(List<Student> student);
}
自動生成的程式碼如下:
public class StudentMapperImpl implements StudentMapper {
@Override
public List<StudentDto> map(List<Student> student) {
if ( student == null ) {
return null;
}
List<StudentDto> list = new ArrayList<StudentDto>( student.size() );
for ( Student student1 : student ) {
list.add( studentToStudentDto( student1 ) );
}
return list;
}
protected StudentDto studentToStudentDto(Student student) {
if ( student == null ) {
return null;
}
StudentDto studentDto = new StudentDto();
studentDto.setId( student.getId() );
studentDto.setName( student.getName() );
return studentDto;
}
}
Set
與Map
型資料的處理方式與List
相似:
@Mapper
public interface StudentMapper {
Set<StudentDto> setConvert(Set<Student> student);
Map<String, StudentDto> mapConvert(Map<String, Student> student);
}
新增預設值
@Mapping註解有兩個很實用的標誌就是常量constant和預設值defaultValue。無論source如何取值,都將始終使用常量值,如果source取值為null,則會使用預設值。修改一下StudentMapper,新增一個constant和一個defaultValue:
@Mapper(componentModel = "spring")
public interface StudentMapper {
@Mapping(target = "id", constant = "-1")
@Mapping(source = "student.name", target = "name", defaultValue = "zzx")
StudentDto toDto(Student student);
}
如果name
不可用,我們會替換為zzx
字串,此外,我們將id
硬編碼為-1
。
@Component
public class StudentMapperImpl implements StudentMapper {
@Override
public StudentDto toDto(Student student) {
if (student == null) {
return null;
}
StudentDto studentDto = new StudentDto();
if (student.getName() != null) {
studentDto.setName(student.getName());
}
else {
studentDto.setName("zzx");
}
studentDto.setId(-1);
return studentDto;
}
}
新增表示式
MapStruct
甚至允許在@Mapping
註解中輸入Java
表示式。你可以設定defaultExpression
:
@Mapper(componentModel = "spring", imports = {LocalDateTime.class, UUID.class})
public interface StudentMapper {
@Mapping(target = "id", expression = "java(UUID.randomUUID().toString())")
@Mapping(source = "student.availability", target = "availability", defaultExpression = "java(LocalDateTime.now())")
StudentDto toDtoWithExpression(Student student);
}
五、依賴注入
如果你使用的是Spring,只需要修改對映器配置,在Mapper註解中新增componentModel = "spring",告訴MapStruct在生成對映器實現類時,支援透過Spring的依賴注入來建立,就不需要在介面中新增INSTANCE欄位了。這次生成的StudentMapperImpl會帶有@Component註解,就可以在其它類中透過@Autowire註解來使用它。
@Mapper(componentModel = "spring")
public interface StudentMapper {}
如果你不使用Spring
, MapStruct
也支援Java CDI
:
@Mapper(componentModel = "cdi")
public interface StudentMapper {}
轉載自https://blog.csdn.net/zhengzhaoyang122/article/details/132657876