公司系统中之前一直有使用组件进行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
复制代码