为什么阿里代码规约要求避免使用 Apache BeanUtils 进行属性复制

缘起

有一次开发过程中,刚好看到小伙伴在调用 set 方法,将数据库中查询出来的 Po 对象的 属性拷贝 到 Vo 对象中,类似这样:

可以看出,Po 和 Vo 两个类的字段绝大部分是一样的,我们一个个地调用 set 方法只是做了一些重复的冗长的操作。 这种操作非常容易出错,因为对象的属性太多,有可能会漏掉一两个,而且肉眼很难察觉

类似这样的操作,我们很容易想到可以通过反射来解决。其实,如此普遍通用的功能,一个 BeanUtils 工具类就可以搞定了。

于是我建议这位小伙伴了解一下 BeanUtils,后来他使用了 Apache BeanUtils.copyProperties 进行属性拷贝,这为程序 挖了一个坑

阿里代码规约

当我们开启阿里代码扫描插件时,如果你使用了 Apache BeanUtils.copyProperties 进行属性拷贝,它会给你一个 非常严重的警告 。因为, Apache BeanUtils性能较差,可以使用 Spring BeanUtils 或者 Cglib BeanCopier 来代替

看到这样的警告,有点让人有点不爽。大名鼎鼎的 Apache 提供的包,居然会存在 性能问题 ,以致于阿里给出了严重的警告。

那么,这个 性能问题究竟是有多严重 呢?毕竟,在我们的应用场景中,如果只是很微小的性能损耗,但是能带来非常大的便利性,还是可以接受的。

带着这个问题。我们来做一个实验,验证一下。

如果对具体的测试方式没有兴趣,可以 跳过直接看结果 哦~

测试方法接口和实现定义

首先,为了测试方便,让我们来定义一个接口,并提供各种实现:

单元测试

然后写一个参数化的单元测试:

测试结果

结果表明, Cglib 的 BeanCopier 的拷贝速度是最快的 ,即使是百万次的拷贝也只需要 10 毫秒! 相比而言,最差的是 Commons 包的 BeanUtils.copyProperties 方法,100 次拷贝测试与表现最好的 Cglib 相差  400 倍 之多。百万次拷贝更是出现了  2600 倍的性能差异!

结果真是让人大跌眼镜。

但是它们为什么会有这么大的差异呢?

原因分析

查看源码,我们会发现 CommonsBeanUtils 主要有以下几个耗时的地方:

  • 输出了大量的 日志 调试信息

  • 重复的对象 类型检查

  • 类型转换

具体的性能和源码分析,可以参考这几篇文章:

几种copyProperties工具类性能比较:https://www.jianshu.com/p/bcbacab3b89e

CGLIB中BeanCopier源码实现:https://www.jianshu.com/p/f8b892e08d26

Java Bean Copy框架性能对比:https://yq.aliyun.com/articles/392185

One more thing

除了性能问题之外,在使用 CommonsBeanUtils 时还有 其他的坑 需要特别小心!

包装类默认值

在进行属性拷贝时,低版本CommonsBeanUtils 为了解决Date为空的问题会导致为目标对象的 原始类型的包装类属性赋予初始值 ,如 Integer 属性默认赋值为 0,尽管你的来源对象该字段的值为 null。

这个在我们的 包装类属性为 null 值时有特殊含义的场景 ,非常容易踩坑!例如搜索条件对象,一般 null 值表示该字段不做限制,而 0 表示该字段的值必须为0。

改用其他工具时

当我们看到阿里的提示,或者你看了这篇文章之后,知道了 CommonsBeanUtils 的性能问题,想要改用 Spring 的 BeanUtils 时, 要特别小心

从方法签名上可以看出,这 两个工具类的名称相同,方法名也相同,甚至连参数个数、类型、名称都相同 。但是 参数的位置是相反的 。因此,如果你想更改的时候,千万要记得,将 target 和 source 两个参数也调换过来!