在项目中,尤其是在服务层,经常要将服务中的Dto实体对象拷贝到Entity对象用于前端展示。反过来也是一样,需要将前端请求对象转换为服务端的数据对象。现在市面上有很多这样的工具包,比如spring框架中就自带了BeanUtils,使我们进行这样的数据操作十分简单快捷。但是一直以来却没有关注到对象属性的拷贝性能问题,最近在想:单个对象的拷贝是快速的,可以不在意太多性能问题,可是如果数量级变大了会怎样呢?如果数量级到达了百万级别,会出现什么样的情况呢?
带着心中的疑问,我首先梳理出来现在有哪些对象拷贝的方式:
- Apache的BeanUtils:BeanUtils是Apache commens组件里面的成员,由Apache提供的一套开源api,用于简化对javaBean的操作,能够对基本类型自动转换。
- Spring的BeanUtils:BeanUtils是Spring框架提供的对Java反射和自省API的包装。其主要目的是利用反射机制对JavaBean的属性进行处理。
- Mapstruct:
MapStruct是一个Java 注释处理器,用于为Java Bean类生成类型安全和高性能的映射器。它使您不必手工编写映射代码,这是一个繁琐且容易出错的任务。该生成器具有合理的默认值和许多内置的类型转换,但是在配置或实现特殊行为时,它会自动退出。
与运行时工作的映射框架相比,MapStruct具有以下优点:- 通过使用普通方法调用而不是反射来快速执行
- 编译时类型安全。只能映射彼此映射的对象和属性,因此不会将订单实体意外映射到客户DTO等。
- 自包含代码 -没有运行时依赖项
- 如果发生以下情况,则在构建时清除错误报告:
- 映射不完整(并非所有目标属性都被映射)
- 映射不正确(找不到正确的映射方法或类型转换)
- 易于调试的映射代码(或手动编辑,例如在生成器中有错误的情况下)
github mapstruct/mapstruct
- BeanCopier:BeanCopier是Cglib包中的一个类,用于对象的复制。目标对象必须先实例化 而且对象必须要有setter方法。
时间出真知,我使用的是相同的数据集,对每种方式都进行了测试。实体类如下:
1 | @Data |
最终的测试结果如下:
数据量 | 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 供你选择。