
在業務項目的開發中,我們經常需要將 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 對象*/@Beforepublic void before() {Role role = new Role(2L, "administrator", "超級管理員");user = new User(1L, "zhangsan", "12345", "17677778888", "123@qq.com", role);}/*** 模擬把 user 對象轉換成 UserRoleDto 對象*/@Testpublic 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對象*/@Testpublic 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
原文鏈接:業務代碼的救星——Java 對象轉換框架 MapStruct 妙用
原出處:公眾號
