優雅的對象轉換解決方案,爲何更推薦 MapStruct 呢?

第一次看到 MapStruct 的時候, 我我的很是的開心。由於其跟我心裏裏面的想法不謀而合。java

1 MapStruct 是什麼?

1.1 JavaBean 的困擾

對於代碼中 JavaBean之間的轉換, 一直是困擾我好久的事情。在開發的時候我看到業務代碼之間有不少的 JavaBean 之間的相互轉化, 很是的影響觀感, 卻又不得不存在。我後來想的一個辦法就是經過反射, 或者本身寫不少的轉換器。整理了一份Java面試寶典完整版PDF已整理成文檔git

第一種經過反射的方法確實比較方便, 可是如今不管是 BeanUtils, BeanCopier 等在使用反射的時候都會影響到性能。雖然咱們能夠進行反射信息的緩存來提升性能。可是像這種的話, 須要類型和名稱都同樣纔會進行映射, 有不少時候, 因爲不一樣的團隊之間使用的名詞不同, 仍是須要不少的手動 set/get 等功能。github

第二種的話就是會很浪費時間, 並且在添加新的字段的時候也要進行方法的修改。不過, 因爲不須要進行反射, 其性能是很高的。面試

1.2 MapStruct 帶來的改變

MapSturct 是一個生成類型安全, 高性能且無依賴的 JavaBean 映射代碼的註解處理器(annotation processor)。緩存

抓一下重點:安全

  1. 註解處理器app

  2. 能夠生成 JavaBean 之間那的映射代碼ide

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

從字面的理解, 咱們能夠知道, 該工具能夠幫咱們實現 JavaBean 之間的轉換, 經過註解的方式。工具

同時, 做爲一個工具類,相比於手寫, 其應該具備便捷, 不容易出錯的特色。性能

2 MapStruct 入門

入門很簡單。我是基於 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
public class Order {

    /**
     *訂單id
     */
    private Long id;

    /**
     * 訂單編號
     */
    private String orderSn;

    /**
     * 收貨人姓名/號碼
     */
    private String receiverKeyword;

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

    /**
     * 訂單類型:0->正常訂單;1->秒殺訂單
     */
    private Integer orderType;

    /**
     * 訂單來源:0->PC訂單;1->app訂單
     */
    private Integer sourceType;
}

對應的查詢參數

@Data
public class OrderQueryParam {
    /**
     * 訂單編號
     */
    private String orderSn;

    /**
     * 收貨人姓名/號碼
     */
    private String receiverKeyword;

    /**
     * 訂單狀態:0->待付款;1->待發貨;2->已發貨;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 註解便可。而後方法上, 入參對應要被轉化的對象, 返回值對應轉化後的對象, 方法名稱可任意。

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個步驟來實現了從 Order 到 OrderQueryParam 的轉換。

那麼, 做爲一個註解處理器, 經過MapStruct 生成的代碼具備怎麼樣的優點呢?

3.1 高性能

這是相對反射來講的, 反射須要去讀取字節碼的內容, 花銷會比較大。而經過 MapStruct 來生成的代碼, 其相似於人手寫。速度上能夠獲得保證。

前面例子中生成的代碼能夠在編譯後看到。在 target/generated-sources/annotations 裏能夠看到。

image

生成的代碼

對應的代碼

@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;
    }
}

能夠看到其生成了一個實現類, 而代碼也相似於咱們手寫, 通俗易懂。

3.2 易於 debug

在咱們生成的代碼中, 咱們能夠輕易的進行 debug。

image

易於 DEBUG

在使用反射的時候, 若是出現了問題, 不少時候是很難找到是什麼緣由的。

3.3 使用相對簡單

若是是徹底映射的, 使用起來確定沒有反射簡單。用相似 BeanUtils 這些工具一條語句就搞定了。可是,若是須要進行特殊的匹配(特殊類型轉換, 多對一轉換等), 其相對來講也是比較簡單的。整理了一份Java面試寶典完整版PDF已整理成文檔

基本上, 使用的時候, 咱們只須要聲明一個接口, 接口下寫對應的方法, 就可使用了。固然, 若是有特殊狀況, 是須要額外處理的。

3.4 代碼獨立

生成的代碼是對立的, 沒有運行時的依賴。

相關文章
相關標籤/搜索