你還在用 BeanUtils來做對象轉換嗎?快試試 MapStruct吧
作者:阿進的寫字臺
https://www.cnblogs.com/homejim/
第一次看到 MapStruct 的時候, 我個人非常的開心。因為其跟我內心里面的想法不謀而合。
1 MapStruct 是什么?
1.1 JavaBean 的困擾
對于代碼中 之間的轉換, 一直是困擾我很久的事情。JavaBean
在開發(fā)的時候我看到業(yè)務代碼之間有很多的 之間的相互轉化, 非常的影響觀感, 卻又不得不存在。我后來想的一個辦法就是通過反射, 或者自己寫很多的轉換器。JavaBean
第一種通過反射的方法確實比較方便, 但是現(xiàn)在無論是 BeanUtils, BeanCopier 等在使用反射的時候都會影響到性能。雖然我們可以進行反射信息的緩存來提高性能。
但是像這種的話, 需要類型和名稱都一樣才會進行映射, 有很多時候, 由于不同的團隊之間使用的名詞不一樣, 還是需要很多的手動 set/get 等功能。
第二種的話就是會很浪費時間, 而且在添加新的字段的時候也要進行方法的修改。不過, 由于不需要進行反射, 其性能是很高的。
1.2 MapStruct 帶來的改變
MapSturct 是一個生成類型安全, 高性能且無依賴的 JavaBean 映射代碼的注解處理器(annotation processor)。
抓一下重點:
注解處理器
可以生成
JavaBean之間那的映射代碼類型安全, 高性能, 無依賴性
從字面的理解, 我們可以知道, 該工具可以幫我們實現(xiàn) 之間的轉換, 通過注解的方式。JavaBean
同時, 作為一個工具類,相比于手寫, 其應該具有便捷, 不容易出錯的特點。
2 MapStruct 入門
入門很簡單。我是基于 Maven 來進行項目 jar 包管理的。
2.1 引入依賴
????????1.3.0.Final
????org.mapstruct
????mapstruct-jdk8
????${org.mapstruct.version}
????org.mapstruct
????mapstruct-processor
????${org.mapstruct.version}
2.2 創(chuàng)建entity和dto對象
該類是從 github 某個訂單系統(tǒng)里面拿下來的部分。
@Data
public?class?Order?{
????/**
?????*訂單id
?????*/
????private?Long?id;
????/**
?????*?訂單編號
?????*/
????private?String?orderSn;
????/**
?????*?收貨人姓名/號碼
?????*/
????private?String?receiverKeyword;
????/**
?????*?訂單狀態(tài):0->待付款;1->待發(fā)貨;2->已發(fā)貨;3->已完成;4->已關閉;5->無效訂單
?????*/
????private?Integer?status;
????/**
?????*?訂單類型:0->正常訂單;1->秒殺訂單
?????*/
????private?Integer?orderType;
????/**
?????*?訂單來源:0->PC訂單;1->app訂單
?????*/
????private?Integer?sourceType;
}
對應的查詢參數(shù)
@Data
public?class?OrderQueryParam?{
????/**
?????*?訂單編號
?????*/
????private?String?orderSn;
????/**
?????*?收貨人姓名/號碼
?????*/
????private?String?receiverKeyword;
????/**
?????*?訂單狀態(tài):0->待付款;1->待發(fā)貨;2->已發(fā)貨;3->已完成;4->已關閉;5->無效訂單
?????*/
????private?Integer?status;
????/**
?????*?訂單類型:0->正常訂單;1->秒殺訂單
?????*/
????private?Integer?orderType;
????/**
?????*?訂單來源:0->PC訂單;1->app訂單
?????*/
????private?Integer?sourceType;
}
2.3 寫 Mapper
Mapper 即映射器, 一般來說就是寫 xxxMapper 接口。
當然, 不一定是以 Mapper 結尾的。只是官方是這么寫的。在本入門例子中,對應的接口如下
import?com.homejim.mapstruct.dto.OrderQueryParam;
import?com.homejim.mapstruct.entity.Order;
import?org.mapstruct.Mapper;
import?org.mapstruct.Mapping;
@Mapper
public?interface?OrderMapper?{
????OrderQueryParam?entity2queryParam(Order?order);
}
簡單的映射(字段和類型都匹配), 只有一個要求, 在接口上寫 @Mapper 注解即可。
然后方法上, 入?yún)晦D化的對象, 返回值對應轉化后的對象, 方法名稱可任意。
2.4 測試
寫一個測試類測試一下。
@Test
public?void?entity2queryParam()?{
????Order?order?=?new?Order();
????order.setId(12345L);
????order.setOrderSn("orderSn");
????order.setOrderType(0);
????order.setReceiverKeyword("keyword");
????order.setSourceType(1);
????order.setStatus(2);
????OrderMapper?mapper?=?Mappers.getMapper(OrderMapper.class);
????OrderQueryParam?orderQueryParam?=?mapper.entity2queryParam(order);
????assertEquals(orderQueryParam.getOrderSn(),?order.getOrderSn());
????assertEquals(orderQueryParam.getOrderType(),?order.getOrderType());
????assertEquals(orderQueryParam.getReceiverKeyword(),?order.getReceiverKeyword());
????assertEquals(orderQueryParam.getSourceType(),?order.getSourceType());
????assertEquals(orderQueryParam.getStatus(),?order.getStatus());
}
測試通過, 沒有任何的問題。
3 MapStruct 分析
上面中, 我寫了3個步驟來實現(xiàn)了從 Order 到 OrderQueryParam 的轉換。
那么, 作為一個注解處理器, 通過MapStruct 生成的代碼具有怎么樣的優(yōu)勢呢?
3.1 高性能
這是相對反射來說的, 反射需要去讀取字節(jié)碼的內容, 花銷會比較大。而通過 MapStruct 來生成的代碼, 其類似于人手寫。速度上可以得到保證。
前面例子中生成的代碼可以在編譯后看到。在 target/generated-sources/annotations 里可以看到。

對應的代碼
@Generated(
????value?=?"org.mapstruct.ap.MappingProcessor",
????date?=?"2019-08-02T00:29:49+0800",
????comments?=?"version:?1.3.0.Final,?compiler:?javac,?environment:?Java?11.0.2?(Oracle?Corporation)"
)
public?class?OrderMapperImpl?implements?OrderMapper?{
????@Override
????public?OrderQueryParam?entity2queryParam(Order?order)?{
????????if?(?order?==?null?)?{
????????????return?null;
????????}
????????OrderQueryParam?orderQueryParam?=?new?OrderQueryParam();
????????orderQueryParam.setOrderSn(?order.getOrderSn()?);
????????orderQueryParam.setReceiverKeyword(?order.getReceiverKeyword()?);
????????orderQueryParam.setStatus(?order.getStatus()?);
????????orderQueryParam.setOrderType(?order.getOrderType()?);
????????orderQueryParam.setSourceType(?order.getSourceType()?);
????????return?orderQueryParam;
????}
}
可以看到其生成了一個實現(xiàn)類, 而代碼也類似于我們手寫, 通俗易懂。
3.2 易于 debug
在我們生成的代碼中, 我們可以輕易的進行 debug。

在使用反射的時候, 如果出現(xiàn)了問題, 很多時候是很難找到是什么原因的。
3.3 使用相對簡單
如果是完全映射的, 使用起來肯定沒有反射簡單。用類似 BeanUtils 這些工具一條語句就搞定了。但是,如果需要進行特殊的匹配(特殊類型轉換, 多對一轉換等), 其相對來說也是比較簡單的。
基本上, 使用的時候, 我們只需要聲明一個接口, 接口下寫對應的方法, 就可以使用了。當然, 如果有特殊情況, 是需要額外處理的。
3.4 代碼獨立
生成的代碼是對立的, 沒有運行時的依賴。
