Java使用MapStruct進行對象映射的流程步驟
引言
MapStruct 是一款基于注解的、用于 Java 對象映射的代碼生成器。借助 MapStruct,我們做對象轉(zhuǎn)換時,只需按照約定指定映射關系,真正的逐字段映射交給 MapStruct 去做即可,可以省去大量手工代碼的編寫。而且,MapStruct 是在編譯期生成映射代碼,若有字段類型不一致的映射,會提前報錯,其生成的代碼更加安全可靠。再者,MapStruct 生成的代碼的執(zhí)行性能與我們手工編寫的代碼無異,遠優(yōu)于市面上流行的幾款基于反射的映射框架(如 BeanUtils、ModelMapper 等)。
本文即以常見的基于 Maven 管理的 Java 項目為基礎,以實際項目中的 VO(值對象)到 DTO(數(shù)據(jù)傳輸對象)的轉(zhuǎn)換為例來演示 MapStruct 的常用功能和使用方式。
寫作本文時,用到的 Java、MapStruct、Lombok 的版本如下:
Java: 17 MapStruct: 1.6.3 Lombok: 1.18.38
1 引入 Maven 依賴
開始前,需要在 pom.xml 引入相應的依賴。本示例工程為了省去編寫 POJO 類冗長的 Setters 和 Getters,使用了 Lombok。
我們知道,Lombok 是在編譯期生成代碼的,而 MapStruct 也是在編譯期生成代碼的。所以兩者應該有個先后順序,Lombok 的代碼生成要在 MapStruct 的代碼生成之前執(zhí)行,否則 MapStruct 會在設置字段時因找不到對應的 Setters 或 Getters 而報錯。因此,除了引入 Lombok 和 MapStruct 依賴外,我們還在 maven-compiler-plugin 的 annotationProcessorPaths 部分指定了兩者的處理順序。
<properties>
<java.version>17</java.version>
<lombok.version>1.18.38</lombok.version>
<mapstruct.version>1.6.3</mapstruct.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
2 基礎用法
依賴引入好后,下面就可以開始對 MapStruct 進行使用了。本小節(jié)先看一下 MapStruct 的基礎用法,下個小節(jié)再探索其高級功能。
我們實現(xiàn)的場景是 VO 到 DTO 的轉(zhuǎn)換。
下面就是 User VO 的源碼,其擁有 id、email、name、yearOfBirth、role 和 createdAt 六個屬性。除了 role 為枚舉類型外,其它均為基礎類型。
package com.example.demo.vo;
@Data
public class User {
private Long id;
private String email;
private String name;
private Integer yearOfBirth;
private Role role;
private Date createdAt;
}
package com.example.demo.vo;
public enum Role {
NORMAL,
ADMIN
}
下面是 User VO 的目標對象 UserDto 的源碼。UserDto 除了和 User 擁有完全相同的字段 id 和 email 外,其它字段要么是名字相同但類型不同(如 role、createdDate)、要么是類型相同但名字不同(如 username)、要么是全新的字段(如 age、newCenturyUser)。
package com.example.demo.dto;
@Data
public class UserDto {
private Long id;
private String email;
private String username;
private Integer age;
private Boolean newCenturyUser;
private String role;
private LocalDateTime createdDate;
}
下面即使用 MapStruct 來為 User 到 UserDto 的轉(zhuǎn)換做映射。
可以看到,我們需要定義一個專門做轉(zhuǎn)換的接口 UserMapper,并在其上配置 @Mapper 注解,然后在接口中定義一個 User 到 UserDto 的轉(zhuǎn)換方法 toUserDto()。
接下來就是字段級的映射配置了:
id和email字段在兩個 POJO 中完全相同,無需配置,MapStruct 會自動幫我們做映射。name到username的映射,因名稱發(fā)生了變化,需要使用@Mapping注解來指定源字段和目的字段的對應關系。yearOfBirth到age的映射,因字段意義發(fā)生變化,需要進行計算??赏ㄟ^在UserMapper接口定義一個默認方法calculateAge,然后在@Mapping映射中使用qualifiedByName指定該實現(xiàn)方法來達成。role字段在源對象中是枚舉類型,而在目的對象中是String類型,我們無需做處理,MapStruct 會自動幫我們將枚舉的name()值設置到目標字段。newCenturyUser是目標對象的一個新字段,其需基于源對象的yearOfBirth字段進行計算,可以通過指定一個表達式(expression)來達成。- 源字段的
createdAt是Date類型,目的字段的createdDate是LocalDateTime類型,可以通過引入一個兩者轉(zhuǎn)換的 Util 類或其它 Mapper 來達成(這里引入了DateTimeUtil工具類,且在@Mapping映射中未指定任何方法,因為 MapStruct 會自動幫我們檢測適用的方法并調(diào)用)。
package com.example.demo.mapper;
@Mapper(uses = DateTimeUtil.class)
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(source = "name", target = "username")
@Mapping(source = "yearOfBirth", target = "age", qualifiedByName = "calculateAge")
@Mapping(target = "newCenturyUser", expression = "java(user.getYearOfBirth() >= 2000)")
@Mapping(source = "createdAt", target = "createdDate")
UserDto toUserDto(User user);
@Named("calculateAge")
default Integer calculateAge(Integer yearOfBirth) {
Calendar calendar = Calendar.getInstance();
return calendar.get(Calendar.YEAR) - yearOfBirth;
}
}
下面即是 DateTimeUtil 工具類的源碼:
package com.example.demo.util;
public class DateTimeUtil {
public LocalDateTime asLocalDateTime(Date date) {
if (null == date) {
return null;
}
return date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
}
}
可以看到,MapStruct 的使用還是比較簡單的:名稱與類型完全相同的字段無需任何額外配置;類型相同但名稱不同的字段則只需指定 source 和 target 即可;需要額外計算的字段,可以通過編寫表達式或引入其它 Mapper 或工具類來實現(xiàn)。
使用 mvn clean package 命令將工程編譯后會發(fā)現(xiàn) MapStruct 在 target/classes 目錄對應位置根據(jù)我們的要求自動生成了 UserMapper 的實現(xiàn)類 UserMapperImpl。
最后,我們編寫一個單元測試類來對上述 Mapper 進行一下測試。
package com.example.demo.mapper;
public class UserMapperTest {
@Test
public void testToUserDto() {
User user = new User();
user.setId(1L);
user.setEmail("larry@larry.com");
user.setName("Larry");
user.setYearOfBirth(2000);
user.setRole(Role.NORMAL);
user.setCreatedAt(new Date());
UserDto userDto = UserMapper.INSTANCE.toUserDto(user);
System.out.println(userDto);
}
}
執(zhí)行 mvn clean test 命令運行如上單元測試后發(fā)現(xiàn)打印的目標對象 UserDto 的字段值與預期一致。
UserDto(id=1, email=larry@larry.com, username=Larry, age=25, newCenturyUser=true, role=NORMAL, createdDate=2025-08-28T08:30:10.568)
3 進階用法
3.1 多個源對象到一個目的對象的映射
若想將多個源對象映射到一個目的對象,@Mapping 配置也很直觀。
package com.example.demo.mapper;
@Mapper
public interface CustomerMapper {
@Mapping(source = "customer.name", target = "name")
@Mapping(source = "customer.yearOfBirth", target = "yearOfBirth")
@Mapping(source = "address.province", target = "province")
@Mapping(source = "address.city", target = "city")
@Mapping(source = "address.street", target = "street")
CustomerDto toCustomerDto(Customer customer, Address address);
}
3.2 嵌套對象和集合對象映射
如果對象之間有嵌套,或者是集合對象,MapStruct 也能很好的勝任對應的轉(zhuǎn)換。
package com.example.demo.vo;
@Data
public class School {
private String name;
private List<Student> students;
}
package com.example.demo.dto;
@Data
public class SchoolDto {
private String name;
private List<StudentDto> students;
}
package com.example.demo.mapper;
@Mapper
public interface SchoolMapper {
SchoolDto toSchoolDto(School school);
List<SchoolDto> toSchoolDtos(List<School> schools);
}
3.3 逆向映射
若已定義過 User 到 UserDto 的映射,現(xiàn)在想做 UserDto 到 User 的轉(zhuǎn)換怎么辦?使用 @InheritInverseConfiguration 注解可以自動生成反向映射規(guī)則,避免重復定義。
package com.example.demo.mapper;
@Mapper
public interface UserMapper {
// @Mapping(xxx)
UserDto toUserDto(User user);
@InheritInverseConfiguration
User toUser(UserDto userDto);
}
3.4 默認值和常量
還可以為映射提供默認值或常量。下面的示例中:當 User 的 name 字段為 null 時,MapStruct 即會為目標字段賦上默認值 Unknown;此外,還可以為目標對象中 String 類型的 level 字段賦上常量值 PRIMARY。
package com.example.demo.mapper;
@Mapper
public interface UserMapper {
@Mapping(source = "name", target = "username", defaultValue = "Unknown")
@Mapping(target = "level", constant = "PRIMARY")
UserDto toUserDto(User user);
}
4 如何與 Spring 框架集成?
欲將 MapStruct 與 Spring 集成的話,只需在 Mapper 的 @Mapper 注解上加上 componentModel = "spring" 即可。
package com.example.demo.mapper;
@Mapper(componentModel = "spring")
public interface UserMapper {
}
這樣,即可取代上面的 UserMapper.INSTANCE 的方式,而直接使用 @Autowired 注解將 UserMapper 進行注入并使用了。
package com.example.demo.mapper;
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testToUserDto() {
// ...
UserDto userDto = userMapper.toUserDto(user);
}
}
5 小結
綜上,本文首先對 MapStruct 工具進行了介紹,然后創(chuàng)建了一個 Maven 示例工程,并將 MapStruct 和 Lombok 依賴引入;然后以實際項目中的 VO 到 DTO 轉(zhuǎn)換為例,介紹了 MapStruct 的基礎功能和高級功能;最后還介紹了 MapStruct 與 Spring 框架的集成。總體上感覺 MapStruct 還是比較易用且高效的。
以上就是Java使用MapStruct進行對象映射的流程步驟的詳細內(nèi)容,更多關于Java MapStruct對象映射的資料請關注腳本之家其它相關文章!
相關文章
java中char對應的ASCII碼的轉(zhuǎn)化操作
這篇文章主要介紹了java中char對應的ASCII碼的轉(zhuǎn)化操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
Springboot jar包 idea 遠程調(diào)試的操作過程
文章介紹了如何在IntelliJ IDEA中遠程調(diào)試Spring Boot項目的Jar包,本文通過圖文并茂的形式給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2024-11-11
Java實現(xiàn)自動獲取法定節(jié)假日詳細代碼
這篇文章主要給大家介紹了關于Java實現(xiàn)自動獲取法定節(jié)假日的相關資料,獲取并處理節(jié)假日數(shù)據(jù)是一個常見需求,特別是在需要安排任務調(diào)度、假期通知等功能的場景中,需要的朋友可以參考下2024-05-05
Java自帶定時任務ScheduledThreadPoolExecutor實現(xiàn)定時器和延時加載功能
今天小編就為大家分享一篇關于Java自帶定時任務ScheduledThreadPoolExecutor實現(xiàn)定時器和延時加載功能,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12

