公司系统中之前一直有使用组件进行Bean copy的操作,只是知道此操作对性能有影响,但是到底有多少影响心里一直没有数。现在对Bean copy进行测试获取量化的结果
目前Bean Copy的主流组件:
- Apache BeanUtils
- Spring BeanUtils
- Cglib BeanCopier
众所周知Apache BeanUtils性能太差,一般不推荐使用。这里不对其进行测试,只测试后面两个组件
测试环境:
- JAVA8
- Spring Boot 2.1.4.RELEASE
- 本地普通台式机
测试代码:
测试功能:循环N次,将TelAppModel 对象中的属性复制到TelAppDto中,统计每种组件花费的时间,花费时间越少的性能越强。
测试POJO类 源类 TelAppModel.java 和目标类 TelAppDto.java,两个类都是简单的pojo类且成员变量相同。
普通的Java set/get方法实现Bean Copy, 代码如下:
public static void copySetGet(TelAppModel source, TelAppDto target){
target.setId(source.getId());
target.setTelPowerSavingMode((byte)0);
target.setTelSecret(source.getTelSecret());
target.setTelAppId(source.getTelAppId());
target.setTelName(source.getTelName());
target.setDesc(source.getDesc());
}
复制代码
Spring BeanUtils实现Bean Copy代码如下:
public static void copyPropertiesSpring(Object source, Object target){
BeanUtils.copyProperties(source, target);
}
复制代码
Cglib BeanCopier实现Bean Copy代码如下:
public static void copyPropertiesCglib(Object source, Object target){
BeanCopier beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false);
beanCopier.copy(source, target, null);
}
复制代码
测试代码: longCount:定义复制执行的次数
// 在执行Bean copy前 先初始化 longCount 个TelAppModel,做为测试素材
for(int i = 0; i < longCount; i++){
appModelSourceList.add(createModel());
}
复制代码
3种Bean copy方法依次调用此test()方法:
// set/get方法
test((a,b) -> copySetGet(a, b));
// spring BeanUtils
test((a,b) -> copyPropertiesSpring(a, b));
// spring BeanCopier
test((a,b) -> copyPropertiesCglib(a, b));
复制代码
// 此方法依次将列表中TelAppModel对象复制到TelAppDto对象中,并打印执行longCount次花费的时间,为了保证结果准确,以上操作执行3次,即3*longCount次
private void test(BiConsumer<TelAppModel, TelAppDto> biConsumer){
int runNum = 3;
for(int k = 0; k < runNum; k++) {
long loopCount = longCount;
long begin = System.currentTimeMillis();
for (int i = 0; i < loopCount; i++) {
TelAppModel telAppModel1 = appModelSourceList.get(i);
TelAppDto telAppDto = new TelAppDto();
biConsumer.accept(telAppModel1, telAppDto);
}
System.out.println((System.currentTimeMillis() - begin));
}
}
复制代码
测试报告:
分别执行1000、10000、100000、1000000次耗时数(毫秒): 详细时间如下:
数据分析:
- ○ set/get方法理论上应该是最快的
- ○ 性能如下: set/get方法 > Cglib BeanCopier > Sprign BeanUtils
- ○ Spring BeanUtils每次第一次循环花费时间特别多,和后面二次不在一个数据量,数据有异常
在执行命令时,通过jvisualvm查看CPU的耗时时间,详细如下:
图表分析:
- Spring BeanUtils第一次执行CachedIntrospectionResults.forClass()方法时,会将class信息从磁盘加载到内存缓存,然后后续的操作会从缓存中获取class信息。这也解释了为什么Spring BeanUtils为什么会比较慢的原因。另外此操作是同步的,如果多线程同时加载相同的class,会出现阻塞的情况。
- Spring BeanUtils的copyProperties占用的CPU也比较多,说明真正执行copy Bean操作依然会花费较多的时间
- 观察上图,发现BeanCopier主要花费的时间是调用create()方法,真正执行copy Bean花费的时间较少,现在我们根据此进行优化,执行Bean Copy时,不是每次执行创建新BeanCopier,而是使用将BeanCopier.create()创建的BeanCopier进行缓存,修改后代码如下:
static BeanCopier beanCopierStatic = BeanCopier.create(TelAppModel.class, TelAppDto.class, false);
public static void copyPropertiesCglibStatic(Object source, Object target){
beanCopierStatic.copy(source, target, null);
}
复制代码
再分别执行1000、10000、100000、1000000次耗时数(毫秒): 详细时间如下:
分析: Cglib BeanCopier优化,性能大大提高。cglib在性能和set/get方法相差不大
结论:
spring beanUtils 和 cglib 性能都还可以接受,如果对性能没有非常苛刻的要求,使用cglib或spring bean utils 问题都问题不大,推荐优先使用cglib
复制代码