「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」。
1.前言
Hello 大家好,我是l拉不拉米
,今天的『十倍程序员』
系列给大家分享Java五大映射框架的介绍以及简单实用,如果您还在用BeanUtils.copyProperties()
,请赶快换成映射框架
2.简介
创建由多层组成的大型 Java 应用程序需要使用多个模型,例如持久性模型、域模型或所谓的 DTO。为不同的应用层使用多个模型将需要我们提供一种 bean 之间的映射方式。
手动执行此操作可以快速创建大量样板代码并消耗大量时间。幸运的是,Java 有多种对象映射框架。
在本文中,我们将比较最流行的五款 Java 映射框架的性能。
3.映射框架
3.1. Dozer
Dozer 是一种映射框架,它使用递归将数据从一个对象复制到另一个对象。该框架不仅能够在 bean 之间复制属性,而且还可以在不同类型之间自动转换。
要使用 Dozer 框架,我们需要将此类依赖项添加到我们的项目中:
<dependency>
<groupId>com.github.dozermapper</groupId>
<artifactId>dozer-core</artifactId>
<version>6.5.0</version>
</dependency>
3.2. Orika
Orika 是一个 bean 到 bean 的映射框架,它递归地将数据从一个对象复制到另一个对象。
Orika 的一般工作原理类似于推土机。两者之间的主要区别在于 Orika 使用字节码生成。这允许以最小的开销生成更快的映射器。
要使用它,我们需要将此类依赖项添加到我们的项目中:
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.4</version>
</dependency>
3.3. MapStruct
MapStruct 是一个自动生成 bean 映射器类的代码生成器。
MapStruct 还具有在不同数据类型之间进行转换的能力。
要将 MapStruct 添加到我们的项目中,我们需要包含以下依赖项:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.3.1.Final</version>
</dependency>
3.4. ModelMapper
ModelMapper 是一个框架,旨在通过根据约定确定对象如何相互映射来简化对象映射。它提供类型安全和重构安全的 API。
要在我们的项目中包含 ModelMapper,我们需要添加以下依赖项:
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.8</version>
</dependency>
3.5. JMapper
JMapper 是一个映射框架,旨在提供一个易于使用的、高性能的 Java Bean 之间的映射。
该框架旨在使用注释和关系映射来应用 DRY 原则。
该框架支持不同的配置方式:基于注解、基于 XML 或基于 API。
要在我们的项目中包含 JMapper,我们需要添加它的依赖项:
<dependency>
<groupId>com.googlecode.jmapper-framework</groupId>
<artifactId>jmapper-core</artifactId>
<version>1.6.1.CR2</version>
</dependency>
4.简单实现
4.1. Orika转换器
Orika 允许完整的 API 实现,这极大地简化了映射器的创建:
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. Dozer转换器
Dozer 需要 XML 映射文件,包含以下部分:
<mappings xmlns="http://dozermapper.github.io/schema/bean-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://dozermapper.github.io/schema/bean-mapping
https://dozermapper.github.io/schema/bean-mapping.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() {
this.mapper = DozerBeanMapperBuilder.create().withMappingFiles("dozer-mapping.xml").build();
}
@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. MapStruct转换器
MapStruct 的定义非常简单,因为它完全基于代码生成:
@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);
}
更多使用案例可以参考我之前写的一篇文章:《优雅的对象转换-MapStruct》
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 无法自行在枚举类型之间进行转换,它需要我们创建自定义映射函数:
@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. ModelMapper转换器
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.最后
可以看到不管使用哪种映射框架,代码量都比使用BeanUtils.copyProperties()要多,不过性能要比BeanUtils.copyProperties()强很多,BeanUtils.copyProperties()使用反射实现,性能较差。
作者个人比较推荐使用mapStruct,在编译期间生成增强代码,性能较高。