本文由 JavaGuide 翻譯自 https://www.baeldung.com/java-performance-mapping-frameworks 。轉載請註明原文地址以及翻譯做者。html
建立由多個層組成的大型 Java 應用程序須要使用多種領域模型,如持久化模型、領域模型或者所謂的 DTO。爲不一樣的應用程序層使用多個模型將要求咱們提供 bean 之間的映射方法。手動執行此操做能夠快速建立大量樣板代碼並消耗大量時間。幸運的是,Java 有多個對象映射框架。在本教程中,咱們將比較最流行的 Java 映射框架的性能。java
綜合平常使用狀況和相關測試數據,我的感受 MapStruct、ModelMapper 這兩個 Bean 映射框架是最佳選擇。git
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 。面試
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
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>
ModelMapper 是一個旨在簡化對象映射的框架,它根據約定肯定對象之間的映射方式。它提供了類型安全的和重構安全的 API。
更多關於 ModelMapper 的內容能夠在官方文檔中找到:http://modelmapper.org/ 。
要使用 ModelMapper 框架,咱們須要添加這樣的依賴到咱們的項目:
<dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>1.1.0</version> </dependency>
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>
爲了可以正確地測試映射,咱們須要有一個源和目標模型。咱們已經建立了兩個測試模型。
第一個是一個只有一個字符串字段的簡單 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。
爲了簡化測試設置的設計,咱們建立了以下所示的轉換器接口:
public interface Converter { Order convert(SourceOrder sourceOrder); DestinationCode convert(SourceCode sourceCode); }
咱們全部的自定義映射器都將實現這個接口。
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); } }
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); } }
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); }
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; }
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); } }
對於性能測試,咱們可使用 Java Microbenchmark Harness,關於如何使用它的更多信息能夠在 這篇文章:https://www.baeldung.com/java-microbenchmark-harness 中找到。
咱們爲每一個轉換器建立了一個單獨的基準測試,並將基準測試模式指定爲 Mode.All。
對於平均運行時間,JMH 返回如下結果(越少越好):
這個基準測試清楚地代表,MapStruct 和 JMapper 都有最佳的平均工做時間。
在這種模式下,基準測試返回每秒的操做數。咱們收到如下結果(越多越好):
在吞吐量模式中,MapStruct 是測試框架中最快的,JMapper 緊隨其後。
這種模式容許測量單個操做從開始到結束的時間。基準給出瞭如下結果(越少越好):
這裏,咱們看到 JMapper 返回的結果比 MapStruct 好得多。
這種模式容許對每一個操做的時間進行採樣。三個不一樣百分位數的結果以下:
全部的基準測試都代表,根據場景的不一樣,MapStruct 和 JMapper 都是不錯的選擇,儘管 MapStruct 對 SingleShotTime 給出的結果要差得多。
對於性能測試,咱們可使用 Java Microbenchmark Harness,關於如何使用它的更多信息能夠在 這篇文章:https://www.baeldung.com/java-microbenchmark-harness 中找到。
咱們爲每一個轉換器建立了一個單獨的基準測試,並將基準測試模式指定爲 Mode.All。
JMH 返回如下平均運行時間結果(越少越好):
該基準清楚地代表,MapStruct 和 JMapper 均具備最佳的平均工做時間。
在這種模式下,基準測試返回每秒的操做數。咱們收到如下結果(越多越好):
在吞吐量模式中,MapStruct 是測試框架中最快的,JMapper 緊隨其後。
這種模式容許測量單個操做從開始到結束的時間。基準給出瞭如下結果(越少越好):
這種模式容許對每一個操做的時間進行採樣。三個不一樣百分位數的結果以下:
儘管簡單示例和實際示例的確切結果明顯不一樣,可是它們的趨勢相同。在哪一種算法最快和哪一種算法最慢方面,兩個示例都給出了類似的結果。
根據咱們在本節中執行的真實模型測試,咱們能夠看出,最佳性能顯然屬於 MapStruct。在相同的測試中,咱們看到 Dozer 始終位於結果表的底部。
在這篇文章中,咱們已經進行了五個流行的 Java Bean 映射框架性能測試:ModelMapper , MapStruct , Orika ,Dozer, JMapper。
示例代碼地址:https://github.com/eugenp/tutorials/tree/master/performance-tests。
做者的其餘開源項目推薦: