【实用工具】MapStruct—性能无限接近原生手写的对象转换工具

优秀借鉴
  1. What is a Data Transfer Object (DTO)?
  2. Java bean mappings, the easy way!
  3. Quick Guide to MapStruct
  4. MapStruct使用指南
  5. 12种 vo2dto 方法,就 BeanUtils.copyProperties 压测最拉胯

1、引入

随着微服务和分布式应用程序迅速占领开发领域,数据完整性和安全性比以往任何时候都更加重要。在这些松散耦合的系统之间,安全的通信渠道和有限的数据传输是最重要的。

大多数时候,终端用户或服务不需要访问模型中的全部数据,而只需要访问某些特定的部分,而数据传输对象(Data Transfer Objects, DTO)经常被用于这些应用中,不仅为了减少网络传输的负担,也可充当防污的角色在系统服务中。

对于DTO的含义借鉴了Stack Overflow上面的答案:DTO就是一种用于封装数据并将其从应用程序的一个子系统发送到另一个子系统的对象。

多层应用程序在不同的对象模型之间编写映射代码是一项乏味且容易出错的事情,这时就需要用到对象转换工具进行辅助开发,而市面上有许多这类对象转换的工具将其余类转换成我们需要的DTO,除了我们所熟悉的Apache和Spring,还有许多其它类型的对象转换工具,接下来将简单对比多种工具并选取MapStruct进行介绍。

2、什么是MapStruct

2.1、概述

MapStruct 是一个代码生成器,用于创建实现Java Bean之间转换的扩展映射器,它基于约定优于配置的方法极大地简化了 Java bean 之间映射的实现,我们只需要创建接口,MapStruct就会在编译时自动创建一个具体的实现进行对象的转换。

MapStruct 旨在通过尽可能自动化来简化这项工作。与其他映射框架相比,MapStruct 在编译时生成 bean 映射,这确保了高性能,允许快速的开发人员反馈和彻底的错误检查。

2.2、横向对比

在市场上的工具类五花八门,这里引用小傅哥的测试数据进行展示。用于对象属性转换有12种,包括:普通的getset、json2Json、Apache属性拷贝、Spring属性拷贝、bean-mapping、bean-mapping-asm、BeanCopier、Orika、Dozer、ModelMapper、JMapper、MapStruct,分别测试这12种属性转换操作分别在一百次、一千次、一万次、十万次、一百万次时候的性能时间对比。

横向对比

  • BeanUtils.copyProperties 是大家代码里最常出现的工具类,但只要你不把它用错成 Apache 包下的,而是使用 Spring 提供的,就基本还不会对性能造成多大影响。
  • 但如果说性能更好,可替代手动get、set的,还是 MapStruct 更好用,因为它本身就是在编译期生成get、set代码,和我们写get、set一样。
  • 其他一些组件包主要基于 AOPASMCGlib,的技术手段实现的,所以也会有相应的性能损耗。

2.3、优势

在对比之后,选择MapStruct的理由如下:

  1. 配置灵活:MapStruct支持通过配置文件或注解来定义映射规则。开发人员可以根据具体需求选择更适合的配置方式。
  2. 集成简单:MapStruct可以与Spring、CDI等常用的Java框架无缝集成。它与其他框架的兼容性良好,使用起来非常方便。
  3. 性能优越:MapStruct通过在编译时生成映射代码,避免了运行时的反射操作,从而提升了映射的性能。它生成的映射代码非常高效,可以满足大部分应用场景的性能需求。

3、快速入门

3.1、Maven

<!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct -->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.4.2.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct-processor -->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.4.2.Final</version>
</dependency>

3.2、POJO

public class Person {
    private String name;
    private int age;
}

public class PersonDTO {
    private String fullName;
    private int yearsOld;
}

3.3、统一映射接口

这一步主要是为了增加拓展性,可去掉

@MapperConfig
public interface IMapping<SOURCE, TARGET> {

    /**
     * @description 映射同名属性
     * @author xbaozi
     * @date 2023/6/27 16:39
     * @param var1      源
     * @return TARGET   结果
     **/
    @Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
    TARGET sourceToTarget(SOURCE var1);

    /**
     * @description 映射同名属性,反向
     * @author xbaozi
     * @date 2023/6/27 16:40
     * @param var1      源
     * @return SOURCE   结果
     **/
    @InheritInverseConfiguration(name = "sourceToTarget")
    SOURCE targetToSource(TARGET var1);
    
}

3.4、业务映射接口

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE, unmappedSourcePolicy = ReportingPolicy.IGNORE)
public interface PersonMapper extends IMapping<Person, PersonDTO> {

    PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);

    @Mapping(source = "name", target = "fullName")
    @Mapping(source = "age", target = "yearsOld")
    @Override
    PersonDTO sourceToTarget(Person person);

    @Override
    Person targetToSource(PersonDTO personDTO);
}

3.5、测试

public class Main {
    public static void main(String[] args) {
        Person person = new Person("xbaoziplus", 18);

        PersonDTO personDTO = PersonMapper.INSTANCE.personToPersonDTO(person);

        System.out.println(personDTO.getFullName());
        System.out.println(personDTO.getYearsOld());
    }
}

3.6、输出

xbaoziplus
18

4、简单分析

前面有说到我们只需要创建映射接口,MapStruct就会在编译时自动创建一个具体的实现进行对象的转换。那么如果想要看到具体的实现该在哪里进行查看呢?其实只要我们进行 mvn clean install 命令之后,就可以在 /target/ generated-sources/annotations/ 路径下找到MapStruct在编译时为我们生成的实现类:

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE, unmappedSourcePolicy = ReportingPolicy.IGNORE)
public class PersonMapperImpl implements PersonMapper {

    @Override
    public PersonDTO personToPersonDTO(Person person) {
        if ( person == null ) {
            return null;
        }

        PersonDTO personDTO = new PersonDTO();

        personDTO.setFullName( person.getName() );
        personDTO.setYearsOld( person.getAge() );

        return personDTO;
    }

    @Override
    public Person personDTOToPerson(PersonDTO personDTO) {
        if ( personDTO == null ) {
            return null;
        }

        Person person = new Person();

        person.setName( personDTO.getFullName() );
        person.setAge( personDTO.getYearsOld() );

        return person;
    }
}

5、拓展使用

在MapStruct中还有许多的使用方式,不如集合的拷贝、流的拷贝等,这类拓展使用大家可以参考Quick Guide to MapStruct进行阅读使用。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈宝子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值