『十倍程序员』Java五大对象映射框架,总有一款适合你😘

1,447 阅读4分钟

「这是我参与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,在编译期间生成增强代码,性能较高。