本文由 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。
做者的其餘開源項目推薦: