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

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

1. 介紹

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

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

2. 常見 Bean 映射框架概覽

2.1. Dozer

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

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

<dependency>
    <groupId>net.sf.dozer</groupId>
    <artifactId>dozer</artifactId>
    <version>5.5.1</version>
</dependency>複製代碼

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

2.2. Orika

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

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

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

<dependency>
    <groupId>ma.glasnost.orika</groupId>
    <artifactId>orika-core</artifactId>
    <version>1.5.2</version>
</dependency>複製代碼

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

2.3. MapStruct

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

要使用 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/mapstruct。

要使用 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-framework/jmapper-core/wiki。

要使用 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/tutorials/tree/master/performance-tests/src/main/java/com/baeldung/performancetests/model/source。

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-microbenchmark-harness 中找到。

咱們爲每一個轉換器建立了一個單獨的基準測試,並將基準測試模式指定爲 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-microbenchmark-harness 中找到。

咱們爲每一個轉換器建立了一個單獨的基準測試,並將基準測試模式指定爲 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/tutorials/tree/master/performance-tests。

開源項目推薦

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

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

公衆號

個人公衆號

相關文章
相關標籤/搜索