Apache的BeanUtils、Spring的BeanUtils、Mapstruct、BeanCopier对象拷贝

在项目中,尤其是在服务层,经常要将服务中的Dto实体对象拷贝到Entity对象用于前端展示。反过来也是一样,需要将前端请求对象转换为服务端的数据对象。现在市面上有很多这样的工具包,比如spring框架中就自带了BeanUtils,使我们进行这样的数据操作十分简单快捷。但是一直以来却没有关注到对象属性的拷贝性能问题,最近在想:单个对象的拷贝是快速的,可以不在意太多性能问题,可是如果数量级变大了会怎样呢?如果数量级到达了百万级别,会出现什么样的情况呢?

带着心中的疑问,我首先梳理出来现在有哪些对象拷贝的方式:

  1. Apache的BeanUtils:BeanUtils是Apache commens组件里面的成员,由Apache提供的一套开源api,用于简化对javaBean的操作,能够对基本类型自动转换。
  2. Spring的BeanUtils:BeanUtils是Spring框架提供的对Java反射和自省API的包装。其主要目的是利用反射机制对JavaBean的属性进行处理。
  3. Mapstruct:

    MapStruct是一个Java 注释处理器,用于为Java Bean类生成类型安全和高性能的映射器。它使您不必手工编写映射代码,这是一个繁琐且容易出错的任务。该生成器具有合理的默认值和许多内置的类型转换,但是在配置或实现特殊行为时,它会自动退出。
    与运行时工作的映射框架相比,MapStruct具有以下优点:

    • 通过使用普通方法调用而不是反射来快速执行
    • 编译时类型安全。只能映射彼此映射的对象和属性,因此不会将订单实体意外映射到客户DTO等。
    • 自包含代码 -没有运行时依赖项
    • 如果发生以下情况,则在构建时清除错误报告:
      • 映射不完整(并非所有目标属性都被映射)
      • 映射不正确(找不到正确的映射方法或类型转换)
    • 易于调试的映射代码(或手动编辑,例如在生成器中有错误的情况下)
      github mapstruct/mapstruct
  1. BeanCopier:BeanCopier是Cglib包中的一个类,用于对象的复制。目标对象必须先实例化 而且对象必须要有setter方法。

时间出真知,我使用的是相同的数据集,对每种方式都进行了测试。实体类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
public class UserVo {

private String id;

private String name;

private Integer age;

private String city;

private String sex;

}

最终的测试结果如下:

数据量 Apache Spring MapStruct BeanCopier
100w 391ms 250ms 45ms 57ms
10w 82ms 34ms 8ms 10ms
1w 30ms 19ms 2ms 7ms
1k 15ms 6ms 1ms 5ms
100 5ms 3ms 1ms 4ms
10 2ms 1ms 1ms 4ms

根据测试结果,我们可以得出在速度方面,MapStruct是最好的,执行速度是 Apache BeanUtils 的10倍、Spring BeanUtils 的 4-5倍、和 BeanCopier 的速度差不多。
由此可以看出,在大数据量级的情况下,MapStruct 和 BeanCopier 都有着较高的性能优势,其中 MapStruct 尤为优秀。如果你仅是在日常处理少量的对象时,选取哪个其实变得并不重要,但数据量大时建议还是使用MapStruct 或 BeanCopier 的方式,提高接口性能。

当然,这个数据并没有考虑占用的内存和CPU的资源问题,仅仅是测试了在相同环境下的执行速度。

最后,如果你问我在日常选取哪种方式?我当然是选择使用 Spring 的 BeanUtils 了。虽然它的性能不是最优的,但是我日常也不会出现数十万级数据量的情况,Spring BeanUtils 对我来说完全够用了,同时在使用spring时不必再引入新的依赖包,何乐而不为不呢。所以来说,适用于自身场景的才是最好的,不必要过分追求性能。
但当有一天你遇到需要处理较大数据量级的对象时,请记得还有性能更加优秀的MapStruct 和 BeanCopier 供你选择。

------ 本文结束------