5種常見Bean映射工具的性能比對

本文由 JavaGuide 翻譯自 https://www.baeldung.com/java... 。轉載請註明原文地址以及翻譯做者。

1. 介紹

建立由多個層組成的大型 Java 應用程序須要使用多種領域模型,如持久化模型、領域模型或者所謂的 DTO。爲不一樣的應用程序層使用多個模型將要求咱們提供 bean 之間的映射方法。手動執行此操做能夠快速建立大量樣板代碼並消耗大量時間。幸運的是,Java 有多個對象映射框架。在本教程中,咱們將比較最流行的 Java 映射框架的性能。html

綜合平常使用狀況和相關測試數據,我的感受 MapStruct、ModelMapper 這兩個 Bean 映射框架是最佳選擇。

2. 常見 Bean 映射框架概覽

2.1. Dozer

Dozer 是一個映射框架,它使用遞歸將數據從一個對象複製到另外一個對象。框架不只可以在 bean 之間複製屬性,還可以在不一樣類型之間自動轉換。java

要使用 Dozer 框架,咱們須要添加這樣的依賴到咱們的項目:git

<dependency>
    <groupId>net.sf.dozer</groupId>
    <artifactId>dozer</artifactId>
    <version>5.5.1</version>
</dependency>

更多關於 Dozer 的內容能夠在官方文檔中找到: http://dozer.sourceforge.net/... ,或者你也能夠閱讀這篇文章:https://www.baeldung.com/dozer程序員

2.2. Orika

Orika 是一個 bean 到 bean 的映射框架,它遞歸地將數據從一個對象複製到另外一個對象。github

Orika 的工做原理與 Dozer 類似。二者之間的主要區別是 Orika 使用字節碼生成。這容許以最小的開銷生成更快的映射器。面試

要使用 Orika 框架,咱們須要添加這樣的依賴到咱們的項目:算法

<dependency>
    <groupId>ma.glasnost.orika</groupId>
    <artifactId>orika-core</artifactId>
    <version>1.5.2</version>
</dependency>

更多關於 Orika 的內容能夠在官方文檔中找到:https://orika-mapper.github.i...,或者你也能夠閱讀這篇文章:https://www.baeldung.com/orik...spring

2.3. MapStruct

MapStruct 是一個自動生成 bean mapper 類的代碼生成器。MapStruct 還可以在不一樣的數據類型之間進行轉換。Github 地址:https://github.com/mapstruct/...segmentfault

要使用 MapStruct 框架,咱們須要添加這樣的依賴到咱們的項目:後端

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.2.0.Final</version>
</dependency>

更多關於 MapStruct 的內容能夠在官方文檔中找到:https://mapstruct.org/,或者你也能夠閱讀這篇文章:https://www.baeldung.com/maps...

要使用 MapStruct 框架,咱們須要添加這樣的依賴到咱們的項目:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.2.0.Final</version>
</dependency>

2.4. ModelMapper

ModelMapper 是一個旨在簡化對象映射的框架,它根據約定肯定對象之間的映射方式。它提供了類型安全的和重構安全的 API。

更多關於 ModelMapper 的內容能夠在官方文檔中找到:http://modelmapper.org/

要使用 ModelMapper 框架,咱們須要添加這樣的依賴到咱們的項目:

<dependency>
  <groupId>org.modelmapper</groupId>
  <artifactId>modelmapper</artifactId>
  <version>1.1.0</version>
</dependency>

2.5. JMapper

JMapper 是一個映射框架,旨在提供易於使用的、高性能的 Java bean 之間的映射。該框架旨在使用註釋和關係映射應用 DRY 原則。該框架容許不一樣的配置方式:基於註釋、XML 或基於 api。

更多關於 JMapper 的內容能夠在官方文檔中找到:https://github.com/jmapper-fr...

要使用 JMapper 框架,咱們須要添加這樣的依賴到咱們的項目:

<dependency>
    <groupId>com.googlecode.jmapper-framework</groupId>
    <artifactId>jmapper-core</artifactId>
    <version>1.6.0.1</version>
</dependency>

3.測試模型

爲了可以正確地測試映射,咱們須要有一個源和目標模型。咱們已經建立了兩個測試模型。

第一個是一個只有一個字符串字段的簡單 POJO,它容許咱們在更簡單的狀況下比較框架,並檢查若是咱們使用更復雜的 bean 是否會發生任何變化。

簡單的源模型以下:

public class SourceCode {
    String code;
    // getter and setter
}

它的目標也很類似:

public class DestinationCode {
    String code;
    // getter and setter
}

源 bean 的實際示例以下:

public class SourceOrder {
    private String orderFinishDate;
    private PaymentType paymentType;
    private Discount discount;
    private DeliveryData deliveryData;
    private User orderingUser;
    private List<Product> orderedProducts;
    private Shop offeringShop;
    private int orderId;
    private OrderStatus status;
    private LocalDate orderDate;
    // standard getters and setters
}

目標類以下圖所示:

public class Order {
    private User orderingUser;
    private List<Product> orderedProducts;
    private OrderStatus orderStatus;
    private LocalDate orderDate;
    private LocalDate orderFinishDate;
    private PaymentType paymentType;
    private Discount discount;
    private int shopId;
    private DeliveryData deliveryData;
    private Shop offeringShop;
    // standard getters and setters
}

整個模型結構能夠在這裏找到:https://github.com/eugenp/tut...

4. 轉換器

爲了簡化測試設置的設計,咱們建立了以下所示的轉換器接口:

public interface Converter {
    Order convert(SourceOrder sourceOrder);
    DestinationCode convert(SourceCode sourceCode);
}

咱們全部的自定義映射器都將實現這個接口。

4.1. OrikaConverter

Orika 支持完整的 API 實現,這大大簡化了 mapper 的建立:

public class OrikaConverter implements Converter{
    private MapperFacade mapperFacade;

    public OrikaConverter() {
        MapperFactory mapperFactory = new DefaultMapperFactory
          .Builder().build();

        mapperFactory.classMap(Order.class, SourceOrder.class)
          .field("orderStatus", "status").byDefault().register();
        mapperFacade = mapperFactory.getMapperFacade();
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
        return mapperFacade.map(sourceOrder, Order.class);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return mapperFacade.map(sourceCode, DestinationCode.class);
    }
}

4.2. DozerConverter

Dozer 須要 XML 映射文件,有如下幾個部分:

<mappings xmlns="http://dozer.sourceforge.net"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://dozer.sourceforge.net
  http://dozer.sourceforge.net/schema/beanmapping.xsd">

    <mapping>
        <class-a>com.baeldung.performancetests.model.source.SourceOrder</class-a>
        <class-b>com.baeldung.performancetests.model.destination.Order</class-b>
        <field>
            <a>status</a>
            <b>orderStatus</b>
        </field>
    </mapping>
    <mapping>
        <class-a>com.baeldung.performancetests.model.source.SourceCode</class-a>
        <class-b>com.baeldung.performancetests.model.destination.DestinationCode</class-b>
    </mapping>
</mappings>

定義了 XML 映射後,咱們能夠從代碼中使用它:

public class DozerConverter implements Converter {
    private final Mapper mapper;

    public DozerConverter() {
        DozerBeanMapper mapper = new DozerBeanMapper();
        mapper.addMapping(
          DozerConverter.class.getResourceAsStream("/dozer-mapping.xml"));
        this.mapper = mapper;
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
        return mapper.map(sourceOrder,Order.class);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return mapper.map(sourceCode, DestinationCode.class);
    }
}

4.3. MapStructConverter

Map 結構的定義很是簡單,由於它徹底基於代碼生成:

@Mapper
public interface MapStructConverter extends Converter {
    MapStructConverter MAPPER = Mappers.getMapper(MapStructConverter.class);

    @Mapping(source = "status", target = "orderStatus")
    @Override
    Order convert(SourceOrder sourceOrder);

    @Override
    DestinationCode convert(SourceCode sourceCode);
}

4.4. JMapperConverter

JMapperConverter 須要作更多的工做。接口實現後:

public class JMapperConverter implements Converter {
    JMapper realLifeMapper;
    JMapper simpleMapper;

    public JMapperConverter() {
        JMapperAPI api = new JMapperAPI()
          .add(JMapperAPI.mappedClass(Order.class));
        realLifeMapper = new JMapper(Order.class, SourceOrder.class, api);
        JMapperAPI simpleApi = new JMapperAPI()
          .add(JMapperAPI.mappedClass(DestinationCode.class));
        simpleMapper = new JMapper(
          DestinationCode.class, SourceCode.class, simpleApi);
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
        return (Order) realLifeMapper.getDestination(sourceOrder);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return (DestinationCode) simpleMapper.getDestination(sourceCode);
    }
}

咱們還須要向目標類的每一個字段添加@JMap註釋。此外,JMapper 不能在 enum 類型之間轉換,它須要咱們建立自定義映射函數:

@JMapConversion(from = "paymentType", to = "paymentType")
public PaymentType conversion(com.baeldung.performancetests.model.source.PaymentType type) {
    PaymentType paymentType = null;
    switch(type) {
        case CARD:
            paymentType = PaymentType.CARD;
            break;

        case CASH:
            paymentType = PaymentType.CASH;
            break;

        case TRANSFER:
            paymentType = PaymentType.TRANSFER;
            break;
    }
    return paymentType;
}

4.5. ModelMapperConverter

ModelMapperConverter 只須要提供咱們想要映射的類:

public class ModelMapperConverter implements Converter {
    private ModelMapper modelMapper;

    public ModelMapperConverter() {
        modelMapper = new ModelMapper();
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
       return modelMapper.map(sourceOrder, Order.class);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return modelMapper.map(sourceCode, DestinationCode.class);
    }
}

5. 簡單的模型測試

對於性能測試,咱們可使用 Java Microbenchmark Harness,關於如何使用它的更多信息能夠在 這篇文章:https://www.baeldung.com/java... 中找到。

咱們爲每一個轉換器建立了一個單獨的基準測試,並將基準測試模式指定爲 Mode.All。

5.1. 平均時間

對於平均運行時間,JMH 返回如下結果(越少越好):

AverageTime

這個基準測試清楚地代表,MapStruct 和 JMapper 都有最佳的平均工做時間。

5.2. 吞吐量

在這種模式下,基準測試返回每秒的操做數。咱們收到如下結果(越多越好):

Throughput

在吞吐量模式中,MapStruct 是測試框架中最快的,JMapper 緊隨其後。

5.3. SingleShotTime

這種模式容許測量單個操做從開始到結束的時間。基準給出瞭如下結果(越少越好):

SingleShotTime

這裏,咱們看到 JMapper 返回的結果比 MapStruct 好得多。

5.4. 採樣時間

這種模式容許對每一個操做的時間進行採樣。三個不一樣百分位數的結果以下:

SampleTime

全部的基準測試都代表,根據場景的不一樣,MapStruct 和 JMapper 都是不錯的選擇,儘管 MapStruct 對 SingleShotTime 給出的結果要差得多。

6. 真實模型測試

對於性能測試,咱們可使用 Java Microbenchmark Harness,關於如何使用它的更多信息能夠在 這篇文章:https://www.baeldung.com/java... 中找到。

咱們爲每一個轉換器建立了一個單獨的基準測試,並將基準測試模式指定爲 Mode.All。

6.1. 平均時間

JMH 返回如下平均運行時間結果(越少越好):

平均時間

該基準清楚地代表,MapStruct 和 JMapper 均具備最佳的平均工做時間。

6.2. 吞吐量

在這種模式下,基準測試返回每秒的操做數。咱們收到如下結果(越多越好):

在吞吐量模式中,MapStruct 是測試框架中最快的,JMapper 緊隨其後。

6.3. SingleShotTime

這種模式容許測量單個操做從開始到結束的時間。基準給出瞭如下結果(越少越好):

6.4. 採樣時間

這種模式容許對每一個操做的時間進行採樣。三個不一樣百分位數的結果以下:

SampleTime

儘管簡單示例和實際示例的確切結果明顯不一樣,可是它們的趨勢相同。在哪一種算法最快和哪一種算法最慢方面,兩個示例都給出了類似的結果。

6.5. 結論

根據咱們在本節中執行的真實模型測試,咱們能夠看出,最佳性能顯然屬於 MapStruct。在相同的測試中,咱們看到 Dozer 始終位於結果表的底部。

7. 總結

在這篇文章中,咱們已經進行了五個流行的 Java Bean 映射框架性能測試:ModelMapper MapStruct Orika ,Dozer, JMapper。

示例代碼地址:https://github.com/eugenp/tut...

開源項目推薦

做者的其餘開源項目推薦:

  1. JavaGuide:【Java學習+面試指南】 一份涵蓋大部分Java程序員所須要掌握的核心知識。
  2. springboot-guide : 適合新手入門以及有經驗的開發人員查閱的 Spring Boot 教程(業餘時間維護中,歡迎一塊兒維護)。
  3. programmer-advancement : 我以爲技術人員應該有的一些好習慣!
  4. spring-security-jwt-guide :從零入門 !Spring Security With JWT(含權限驗證)後端部分代碼。

公衆號

個人公衆號

相關文章
相關標籤/搜索