Java使用MapStruct進行對象映射的流程步驟
引言
MapStruct 是一款基于注解的、用于 Java 對象映射的代碼生成器。借助 MapStruct,我們做對象轉(zhuǎn)換時,只需按照約定指定映射關(guān)系,真正的逐字段映射交給 MapStruct 去做即可,可以省去大量手工代碼的編寫。而且,MapStruct 是在編譯期生成映射代碼,若有字段類型不一致的映射,會提前報錯,其生成的代碼更加安全可靠。再者,MapStruct 生成的代碼的執(zhí)行性能與我們手工編寫的代碼無異,遠優(yōu)于市面上流行的幾款基于反射的映射框架(如 BeanUtils、ModelMapper 等)。
本文即以常見的基于 Maven 管理的 Java 項目為基礎(chǔ),以實際項目中的 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 會在設(shè)置字段時因找不到對應的 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 基礎(chǔ)用法
依賴引入好后,下面就可以開始對 MapStruct 進行使用了。本小節(jié)先看一下 MapStruct 的基礎(chǔ)用法,下個小節(jié)再探索其高級功能。
我們實現(xiàn)的場景是 VO 到 DTO 的轉(zhuǎn)換。
下面就是 User VO 的源碼,其擁有 id、email、name、yearOfBirth、role 和 createdAt 六個屬性。除了 role 為枚舉類型外,其它均為基礎(chǔ)類型。
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注解來指定源字段和目的字段的對應關(guān)系。yearOfBirth到age的映射,因字段意義發(fā)生變化,需要進行計算??赏ㄟ^在UserMapper接口定義一個默認方法calculateAge,然后在@Mapping映射中使用qualifiedByName指定該實現(xiàn)方法來達成。role字段在源對象中是枚舉類型,而在目的對象中是String類型,我們無需做處理,MapStruct 會自動幫我們將枚舉的name()值設(shè)置到目標字段。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 小結(jié)
綜上,本文首先對 MapStruct 工具進行了介紹,然后創(chuàng)建了一個 Maven 示例工程,并將 MapStruct 和 Lombok 依賴引入;然后以實際項目中的 VO 到 DTO 轉(zhuǎn)換為例,介紹了 MapStruct 的基礎(chǔ)功能和高級功能;最后還介紹了 MapStruct 與 Spring 框架的集成??傮w上感覺 MapStruct 還是比較易用且高效的。
以上就是Java使用MapStruct進行對象映射的流程步驟的詳細內(nèi)容,更多關(guān)于Java MapStruct對象映射的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
深入解析JVM之內(nèi)存結(jié)構(gòu)及字符串常量池(推薦)
Java作為一種平臺無關(guān)性的語言,其主要依靠于Java虛擬機——JVM,接下來通過本文給大家介紹JVM之內(nèi)存結(jié)構(gòu)及字符串常量池的相關(guān)知識,需要的朋友可以參考下2020-07-07
Mybatis-plus中的@EnumValue注解使用詳解
這篇文章主要介紹了Mybatis-plus中的@EnumValue注解使用詳解,在PO類中,如果我們直接使用枚舉類型去映射數(shù)據(jù)庫的對應字段保存時,往往就會因為類型不匹配導致映射失敗,Mybatis-plus提供了一種解決辦法,就是使用@EnumValue注解,需要的朋友可以參考下2024-02-02
maven中的maven-antrun-plugin插件示例詳解
maven-antrun-plugin?是?Maven?生態(tài)中一個強大的工具,尤其適合需要復用?Ant?腳本或?qū)崿F(xiàn)復雜構(gòu)建邏輯的場景,然而,過度使用可能導致構(gòu)建腳本復雜化,建議權(quán)衡利弊后合理使用,這篇文章主要介紹了maven中的maven-antrun-plugin插件詳解,需要的朋友可以參考下2025-06-06
詳解Guava Cache本地緩存在Spring Boot應用中的實踐
Guava Cache是一個全內(nèi)存的本地緩存實現(xiàn),本文將講述如何將 Guava Cache緩存應用到 Spring Boot應用中。具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-01-01
java中for循環(huán)執(zhí)行的順序圖文詳析
關(guān)于java的for循環(huán)想必大家非常熟悉,它是java常用的語句之一,這篇文章主要給大家介紹了關(guān)于java中for循環(huán)執(zhí)行順序的相關(guān)資料,需要的朋友可以參考下2021-06-06

