1. 程式人生 > 實用技巧 >MapStruct物件轉換

MapStruct物件轉換

第一次看到MapStruct的時候, 我個人非常的開心。因為其跟我內心裡面的想法不謀而合。

1 MapStruct 是什麼?

1.1 JavaBean 的困擾

對於程式碼中JavaBean之間的轉換, 一直是困擾我很久的事情。

在開發的時候我看到業務程式碼之間有很多的JavaBean之間的相互轉化, 非常的影響觀感, 卻又不得不存在。我後來想的一個辦法就是通過反射, 或者自己寫很多的轉換器。

第一種通過反射的方法確實比較方便, 但是現在無論是BeanUtils,BeanCopier等在使用反射的時候都會影響到效能。雖然我們可以進行反射資訊的快取來提高效能。

但是像這種的話, 需要型別和名稱都一樣才會進行對映, 有很多時候, 由於不同的團隊之間使用的名詞不一樣, 還是需要很多的手動 set/get 等功能。

第二種的話就是會很浪費時間, 而且在新增新的欄位的時候也要進行方法的修改。不過, 由於不需要進行反射, 其效能是很高的。

1.2MapStruct帶來的改變

MapSturct是一個生成型別安全, 高效能且無依賴的JavaBean對映程式碼的註解處理器(annotation processor)。

抓一下重點:

  1. 註解處理器

  2. 可以生成JavaBean之間那的對映程式碼

  3. 型別安全, 高效能, 無依賴性

從字面的理解, 我們可以知道, 該工具可以幫我們實現JavaBean之間的轉換, 通過註解的方式。

同時, 作為一個工具類,相比於手寫, 其應該具有便捷, 不容易出錯的特點。

2MapStruct
入門

入門很簡單。我是基於Maven來進行專案 jar 包管理的。

2.1 引入依賴

<properties>
<org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
</properties>

<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>

<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>

2.2建立entity和dto物件

該類是從 github 某個訂單系統裡面拿下來的部分。

@Data
publicclassOrder{

/**
*訂單id
*/
privateLongid;

/**
*訂單編號
*/
privateStringorderSn;

/**
*收貨人姓名/號碼
*/
privateStringreceiverKeyword;

/**
*訂單狀態:0->待付款;1->待發貨;2->已發貨;3->已完成;4->已關閉;5->無效訂單
*/
privateIntegerstatus;

/**
*訂單型別:0->正常訂單;1->秒殺訂單
*/
privateIntegerorderType;

/**
*訂單來源:0->PC訂單;1->app訂單
*/
privateIntegersourceType;
}

對應的查詢引數

@Data
publicclassOrderQueryParam{
/**
*訂單編號
*/
privateStringorderSn;

/**
*收貨人姓名/號碼
*/
privateStringreceiverKeyword;

/**
*訂單狀態:0->待付款;1->待發貨;2->已發貨;3->已完成;4->已關閉;5->無效訂單
*/
privateIntegerstatus;

/**
*訂單型別:0->正常訂單;1->秒殺訂單
*/
privateIntegerorderType;

/**
*訂單來源:0->PC訂單;1->app訂單
*/
privateIntegersourceType;


}

2.3寫 Mapper

Mapper即對映器, 一般來說就是寫xxxMapper介面。

當然, 不一定是以Mapper結尾的。只是官方是這麼寫的。在本入門例子中,對應的介面如下

importcom.homejim.mapstruct.dto.OrderQueryParam;
importcom.homejim.mapstruct.entity.Order;
importorg.mapstruct.Mapper;
importorg.mapstruct.Mapping;

@Mapper
publicinterfaceOrderMapper{

OrderQueryParamentity2queryParam(Orderorder);

}

簡單的對映(欄位和型別都匹配), 只有一個要求, 在介面上寫@Mapper註解即可。

然後方法上, 入參對應要被轉化的物件, 返回值對應轉化後的物件, 方法名稱可任意。

2.4 測試

寫一個測試類測試一下。

@Test
publicvoidentity2queryParam(){
Orderorder=newOrder();
order.setId(12345L);
order.setOrderSn("orderSn");
order.setOrderType(0);
order.setReceiverKeyword("keyword");
order.setSourceType(1);
order.setStatus(2);

OrderMappermapper=Mappers.getMapper(OrderMapper.class);
OrderQueryParamorderQueryParam=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個步驟來實現了從OrderOrderQueryParam的轉換。

那麼, 作為一個註解處理器, 通過MapStruct生成的程式碼具有怎麼樣的優勢呢?

3.1 高效能

這是相對反射來說的, 反射需要去讀取位元組碼的內容, 花銷會比較大。

而通過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:Java11.0.2(OracleCorporation)"
)
publicclassOrderMapperImplimplementsOrderMapper{

@Override
publicOrderQueryParamentity2queryParam(Orderorder){
if(order==null){
returnnull;
}

OrderQueryParamorderQueryParam=newOrderQueryParam();

orderQueryParam.setOrderSn(order.getOrderSn());
orderQueryParam.setReceiverKeyword(order.getReceiverKeyword());
orderQueryParam.setStatus(order.getStatus());
orderQueryParam.setOrderType(order.getOrderType());
orderQueryParam.setSourceType(order.getSourceType());

returnorderQueryParam;
}
}

可以看到其生成了一個實現類, 而程式碼也類似於我們手寫, 通俗易懂。

3.2 易於 debug

在我們生成的程式碼中, 我們可以輕易的進行 debug。

易於 DEBUG

在使用反射的時候, 如果出現了問題, 很多時候是很難找到是什麼原因的。

3.3 使用相對簡單

如果是完全對映的, 使用起來肯定沒有反射簡單。用類似BeanUtils這些工具一條語句就搞定了。但是,如果需要進行特殊的匹配(特殊型別轉換, 多對一轉換等), 其相對來說也是比較簡單的。

基本上, 使用的時候, 我們只需要宣告一個介面, 介面下寫對應的方法, 就可以使用了。當然, 如果有特殊情況, 是需要額外處理的。

3.4 程式碼獨立

生成的程式碼是對立的, 沒有執行時的依賴。