在業務項目的開發中,我們經常需要将 Java 對象進行轉換,比如從将外部微服務得到的對象轉換為本域的業務對象
domainobject
,将
domainobject
轉為資料持久層的
dataobject
,将
domainobject
轉換為
DTO
以便傳回給外部調用方等。在轉換時大部分屬性都是相同的,隻有少部分的不同,如果手工編寫轉換代碼,會很繁瑣。這時我們可以通過一些對象轉換架構來更友善的做這件事情。
這樣的對象轉換架構有不少,比較有名的有 ModelMapper 和 MapStruct。它們所使用的實作技術不同,ModelMapper 是基于反射的,通過反射來查找實體對象的字段,并讀取或寫入值,這樣的方式實作原理簡單,但性能很差。與 ModelMapper 架構不同的是,MapStruct 是基于編譯階段代碼生成的,生成的轉換代碼在運作的時候跟一般的代碼一樣,沒有額外的性能損失。本文重點介紹 MapStruct。
業務場景
假設現在有這麼個場景,從資料庫查詢出來了一個 user 對象(包含 id,使用者名,密碼,手機号,郵箱,角色這些字段)和一個對應的角色對象 role(包含 id,角色名,角色描述這些字段),現在在
controller
需要用到 user 對象的 id,使用者名,和角色對象的角色名三個屬性。一種方式是直接把兩個對象傳遞到
controller
層,但是這樣會多出很多沒用的屬性。更通用的方式是需要用到的屬性封裝成一個類(DTO),通過傳輸這個類的執行個體來完成資料傳輸。
實作方式之使用傳統方式
如下:
User.java
@AllArgsConstructor
@Data
public class User {
private Long id;
private String username;
private String password;
private String phoneNum;
private String email;
private Role role;
}
Role.java
@AllArgsConstructor
@Data
public class Role {
private Long id;
private String roleName;
private String description;
}
UserRoleDto.java
@Data
public class UserRoleDto {
/**
* 使用者id
*/
private Long userId;
/**
* 使用者名
*/
private String name;
/**
* 角色名
*/
private String roleName;
}
MainTest.java
測試類,模拟将 user 對象轉換成 UserRoleDto 對象
public class MainTest {
User user = null;
/**
* 模拟從資料庫中查出 user 對象
*/
@Before
public void before() {
Role role = new Role(2L, "administrator", "超級管理者");
user = new User(1L, "zhangsan", "12345", "17677778888", "[email protected]", role);
}
/**
* 模拟把 user 對象轉換成 UserRoleDto 對象
*/
@Test
public void test1() {
UserRoleDto userRoleDto = new UserRoleDto();
userRoleDto.setUserId(user.getId());
userRoleDto.setName(user.getUsername());
userRoleDto.setRoleName(user.getRole().getRoleName());
System.out.println(userRoleDto);
}
}
運作結果
上邊的代碼或許暫時看起來還是比較簡潔的,但是我們需要注意的一點就是平時業務開發中的對象屬性遠不是上述代碼中簡簡單單的幾個字段,有可能會有數十個字段,同理也會數十個對象需要轉換,我們如果還是通過 getter、setter 的方式把一個對象屬性值複制到另一個對象中去還是非常麻煩的,不過不用擔心,今天要介紹給大家的 MapStruct 就是用于解決這種問題的。
實作方式之使用 MapStruct
這裡我們沿用上述代碼中的基本對象
User.java
、
Role.java
、
UserRoleDto.java
。然後建立一個
UserRoleMapper.java
,這個來用來定義
User.java
、
Role.java
和
UserRoleDto.java
之間屬性對應規則。
在這之前我們需要引入 MapStruct 的 pom 引用:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.3.0.Final</version>
</dependency>
UserRoleMapper.java
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
/**
* @Mapper 定義這是一個MapStruct對象屬性轉換接口,在這個類裡面規定轉換規則
* 在項目建構時,會自動生成改接口的實作類,這個實作類将實作對象屬性值複制
*/
@Mapper
public interface UserRoleMapper {
/**
* 擷取該類自動生成的實作類的執行個體
* 接口中的屬性都是 public static final 的
* 方法都是public abstract 的
*/
UserRoleMapper INSTANCES = Mappers.getMapper(UserRoleMapper.class);
/**
* 這個方法就是用于實作對象屬性複制的方法
*
* @Mapping 用來定義屬性複制規則
* source 指定源對象屬性
* target 指定目标對象屬性
*
* @param user 這個參數就是源對象,也就是需要被複制的對象
* @return 傳回的是目标對象,就是最終的結果對象
*/
@Mappings({
@Mapping(source = "id", target = "userId"),
@Mapping(source = "username", target = "name"),
@Mapping(source = "role.roleName", target = "roleName")
})
UserRoleDto toUserRoleDto(User user);
}
測試一下結果
MainTest.java
/**
* 模拟通過MapStruct把user對象轉換成UserRoleDto對象
*/
@Test
public void test2() {
UserRoleDto userRoleDto = UserRoleMapper.INSTANCES.toUserRoleDto(user);
System.out.println(userRoleDto);
}
呃,很明顯,運作竟然報錯了,具體異常如下:
核心是這一句 :
java.lang.ClassNotFoundException:Cannotfind implementationfortop.zhoudl.mapstruct.UserRoleMapper
,也就是說沒有找到 UserRoleMapper 類的實作類。
通過查閱一些資料可得:
MapStruct 是一個可以處理注解的Java編譯器插件,可以在指令行中使用,也可以在 IDE 中使用。MapStruc t有一些預設配置,但是也為使用者提供了自己進行配置的途徑。缺點就是這玩意在使用工具自帶的編譯器時不會生成實作類,需要通過 maven 的方式來進行編譯,然後才會生成實作類。
是以我們需要增加一個編譯插件到 pom 檔案中:
<!-- 引入 processor -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.0.Final</version>
<scope>provided</scope>
</dependency>
<!--為 Maven compile plugin 設定 annotation processor -->
<plugins>
<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>1.2.0.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
然後我們運作程式就可以得到自己想要的結果了
安裝 MapStruct 插件
使用 MapStruct,還有一個缺點就是,當屬性改名的時候,因為在 Mapper 上注解中配置的名字是在字元串裡面,是以不會自動同步的。是以 MapStruct 提供了一個插件來解決這個問題,同時還提供代碼自動提示、點選跳轉到實作等功能。
關于插件的更多資訊,參見 MapStruct support for IntelliJ IDEA
安裝插件的過程
在 IDEA 中依次打開 File - > Settings - > Plugins
然後在 Markeyplace 搜尋框中輸入 mapstruct,點選 install,然後重新開機 IDE 即可。
一些可能會出現的問題
- 找不到注釋處理程式:在 pom.xml 中增加 mapstruct-processor 的依賴
- 沒有找到實作類:在 pom.xml 中加入對 mapstruct-processor 的依賴
- 在 IDEA 裡面 enable Annotation Processor
- 使用 Lombok 的情況下,編譯時報 Data 類的 setter/getter 找不到:把 lombok 加入到annotationProcessorPath,如下圖
總結
MapSturct
是一個生成類型安全, 高性能且無依賴的 JavaBean 映射代碼的注解處理器(annotation processor)。
作為一個注解處理器, 通過
MapStruct
生成的代碼具有怎麼樣的優勢呢?抓一下重點:
- 注解處理器
- 可以生成
之間的映射代碼JavaBean
- 類型安全, 高性能, 無依賴性
高性能
這是相對反射來說的, 反射需要去讀取位元組碼的内容, 花銷會比較大。而通過
MapStruct
來生成的代碼, 其類似于人手寫,代碼執行速度上可以得到保證。(前面例子中生成的代碼可以在編譯後看到,在項目的
target/generated-sources/annotations
目錄裡可以看到具體代碼)。
易于 debug
在我們生成的代碼中, 我們可以輕易的進行 debug。但是如果是使用反射實作代碼的時候, 一旦出現了問題, 很多時候是比較難找到原因。
使用相對簡單
如果是完全映射的, 使用起來肯定沒有反射簡單。用類似
BeanUtils
這些工具一條語句就搞定了。但是,如果需要進行特殊的比對(特殊類型轉換, 多對一轉換等), MapStruct 的優勢就比較明顯了,基本上我們隻需要在使用的時候聲明一個接口, 接口下寫對應的方法, 就可以使用了(當然, 如果有特殊情況, 是需要額處理一下的)。
代碼獨立
生成的代碼是對立的, 沒有運作時的依賴
原作者:zhoudl