在業務項目的開發中,咱們常常須要將 Java 對象進行轉換,好比從將外部微服務獲得的對象轉換爲本域的業務對象 domain object
,將 domain object
轉爲數據持久層的 data object
,將 domain object
轉換爲 DTO
以便返回給外部調用方等。在轉換時大部分屬性都是相同的,只有少部分的不一樣,若是手工編寫轉換代碼,會很繁瑣。這時咱們能夠經過一些對象轉換框架來更方便的作這件事情。java
這樣的對象轉換框架有很多,比較有名的有 ModelMapper 和 MapStruct。它們所使用的實現技術不一樣,ModelMapper 是基於反射的,經過反射來查找實體對象的字段,並讀取或寫入值,這樣的方式實現原理簡單,但性能不好。與 ModelMapper 框架不一樣的是,MapStruct 是基於編譯階段代碼生成的,生成的轉換代碼在運行的時候跟通常的代碼同樣,沒有額外的性能損失。本文重點介紹 MapStruct。數據庫
假設如今有這麼個場景,從數據庫查詢出來了一個 user 對象(包含 id,用戶名,密碼,手機號,郵箱,角色這些字段)和一個對應的角色對象 role(包含 id,角色名,角色描述這些字段),如今在 controller
須要用到 user 對象的 id,用戶名,和角色對象的角色名三個屬性。一種方式是直接把兩個對象傳遞到 controller
層,可是這樣會多出不少沒用的屬性。更通用的方式是須要用到的屬性封裝成一個類(DTO),經過傳輸這個類的實例來完成數據傳輸。apache
以下:安全
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.javaapp
@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.javadom
測試類,模擬將 user 對象轉換成 UserRoleDto 對象maven
public class MainTest {
User user = null;
/** * 模擬從數據庫中查出 user 對象 */
@Before
public void before() {
Role role = new Role(2L, "administrator", "超級管理員");
user = new User(1L, "zhangsan", "12345", "17677778888", "123@qq.com", 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);
}
}
複製代碼
運行結果 ide
上邊的代碼或許暫時看起來仍是比較簡潔的,可是咱們須要注意的一點就是平時業務開發中的對象屬性遠不是上述代碼中簡簡單單的幾個字段,有可能會有數十個字段,同理也會數十個對象須要轉換,咱們若是仍是經過 getter、setter 的方式把一個對象屬性值複製到另外一個對象中去仍是很是麻煩的,不過不用擔憂,今天要介紹給你們的 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: Cannot find implementation for top.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,還有一個缺點就是,當屬性更名的時候,由於在 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。可是若是是使用反射實現代碼的時候, 一旦出現了問題, 不少時候是比較難找到緣由。
若是是徹底映射的, 使用起來確定沒有反射簡單。 用相似 BeanUtils
這些工具一條語句就搞定了。 可是,若是須要進行特殊的匹配(特殊類型轉換, 多對一轉換等), MapStruct 的優點就比較明顯了,基本上咱們只須要在使用的時候聲明一個接口, 接口下寫對應的方法, 就可使用了(固然, 若是有特殊狀況, 是須要額處理一下的)。
生成的代碼是對立的, 沒有運行時的依賴
本文首發於微信公衆號 【程序猿雜貨鋪】,關注公衆號,獲取更多精彩文章!