对象属性拷贝工具类的性能比较

 一、对象属性拷贝工具类                                                                                 

    ”天下武功,唯快不破“。在互联网行业中体现的更加淋淋尽致。我们在业务系统会经常遇到业务对象间属性的拷贝,对如外接口一般都使用特定的DTO对象,而不会使用领域模型,以避免两者的变动互相影响。我们不仅要关注“快”,还要注重CPU的稳定即避免CPU使用的大起大落现象。如何高效完成属性的拷贝并降低对CPU的使用率或避免CPU的抖动。

相关博文已经有很多,为什么还要自己在一篇类似的哪?原因有二:一是加深理解二是比较各自优劣。目前对象间属性的拷贝常用的方法大致如下:

  • 手动拷贝(set)
  • 动态代理

       cglib版本:net.sf.cglib.beans.BeanCopier.copy(Object from, Object to, Converter converter)

  • 反射机制

       Spring版本:org.springframework.beans.BeanUtils.copyProperties(Object source, Object target) 

       Apache版本:org.apache.commons.beanutils.PropertyUtils.copyProperties(Object dest, Object orig) 

                          org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig)

      DozerMapper

二、实践说明性能优劣                                                                                    

    1、环境

      WIN7 i5,12G内存,

      JVM: 

           java version "1.8.0_51"
           Java(TM) SE Runtime Environment (build 1.8.0_51-b16)
           Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode)

      依赖jar及版本

      

    2、代码结构

      

package test;

/**
 * @author wy
 *
 */
public interface IMethodCallBack {
    public String getMethodName();

    public DestBean callBack(SourceBean sourceBean) throws Exception;
}
View Code
package test;

/**
 * 
 * @author wy
 *
 */
public class CopyProcessor {
    public int count;

    public CopyProcessor(int count) {
        this.count = count;
        System.out.println("性能测试=========" + this.count + "=========");
    }

    public void processor(IMethodCallBack methodCallBack, SourceBean sourceBean) throws Exception {
        long begin = System.currentTimeMillis();
        DestBean destBean = null;
        System.out.println(methodCallBack.getMethodName() + "开始进行测试");
        for (int i = 0; i < count; i++) {
            destBean = methodCallBack.callBack(sourceBean);
        }
        long end = System.currentTimeMillis();
        System.out.println(methodCallBack.getMethodName() + " 耗时 = " + (end - begin) + " 毫秒");

        System.out.println(destBean.getPid());
        System.out.println(destBean.getUserId());
        System.out.println(destBean.getSubTitle());
        System.out.println(destBean.getAlias());
        System.out.println(destBean.getActor());
        System.out.println(destBean.getShortDesc());

        System.out.println("----------------------------------------");
    }

}
View Code
package test;

import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.beanutils.PropertyUtils;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.BeanUtils;

import net.sf.cglib.beans.BeanCopier;

/**
 * 
 * @author wy
 *
 */
public class PerformanceTest {
    public SourceBean sourceBean = null;
    public IMethodCallBack manualCopy = null;
    public IMethodCallBack cglib = null;
    public IMethodCallBack springBeanUtils = null;
    public IMethodCallBack apachePropertyUtils = null;
    public IMethodCallBack apacheBeanUtils = null;

    @Before
    public void init() {
        // 初始化数据
        sourceBean = new SourceBean();
        sourceBean.setPid(Long.valueOf(1001));
        sourceBean.setUserId(Long.valueOf(123));
        sourceBean.setSubTitle("人再囧途之港囧");
        sourceBean.setAlias("港囧");
        Map<String, String> map = new LinkedHashMap<String, String>();
        map.put("主演1", "徐峥");
        map.put("主演2", "包贝尔");
        map.put("主演3", "赵薇");
        sourceBean.setActor(map);
        sourceBean.setShortDesc("徐来和小舅子抱着各自不同目的来到香港,展开了一段阴差阳错、啼笑皆非的旅程,最终两人获得友谊并懂得了人生真谛。");

        // 手动设置属性
        manualCopy = new IMethodCallBack() {

            @Override
            public String getMethodName() {
                return "manual copy";
            }

            @Override
            public DestBean callBack(SourceBean sourceBean) throws Exception {
                DestBean destBean = new DestBean();
                destBean.setActor(sourceBean.getActor());
                destBean.setPid(sourceBean.getPid());
                destBean.setUserId(sourceBean.getUserId().intValue());
                destBean.setShortDesc(sourceBean.getShortDesc());
                destBean.setSubTitle(sourceBean.getSubTitle());
                destBean.setAlias(sourceBean.getAlias());

                return destBean;
            }

        };

        // Cglib
        cglib = new IMethodCallBack() {
            BeanCopier beanCopier = BeanCopier.create(SourceBean.class, DestBean.class, false);

            @Override
            public String getMethodName() {
                return "net.sf.cglib.beans.BeanCopier.create";
            }

            @Override
            public DestBean callBack(SourceBean sourceBean) throws Exception {
                DestBean destBean = new DestBean();
                beanCopier.copy(sourceBean, destBean, null);
                return destBean;
            }

        };

        // Spring BeanUtils
        springBeanUtils = new IMethodCallBack() {

            @Override
            public String getMethodName() {
                return "org.springframework.beans.BeanUtils.copyProperties";
            }

            @Override
            public DestBean callBack(SourceBean sourceBean) throws Exception {
                DestBean destBean = new DestBean();
                BeanUtils.copyProperties(sourceBean, destBean);
                return destBean;
            }

        };

        // Apache PropertyUtils
        apachePropertyUtils = new IMethodCallBack() {

            @Override
            public String getMethodName() {
                return "org.apache.commons.beanutils.PropertyUtils.copyProperties";
            }

            @Override
            public DestBean callBack(SourceBean sourceBean) throws Exception {
                DestBean destBean = new DestBean();
                PropertyUtils.copyProperties(destBean, sourceBean);
                return destBean;
            }

        };

        // Apache BeanUtils
        apacheBeanUtils = new IMethodCallBack() {

            @Override
            public String getMethodName() {
                return "org.apache.commons.beanutils.BeanUtils.copyProperties";
            }

            @Override
            public DestBean callBack(SourceBean sourceBean) throws Exception {
                DestBean destBean = new DestBean();
                org.apache.commons.beanutils.BeanUtils.copyProperties(destBean, sourceBean);
                return destBean;
            }

        };
    }

    // 测试一百次性能测试
    @Test
    public void perform100() throws Exception {
        CopyProcessor processor100 = new CopyProcessor(100);
        processor100.processor(manualCopy, sourceBean);
        processor100.processor(cglib, sourceBean);
        processor100.processor(springBeanUtils, sourceBean);
        processor100.processor(apachePropertyUtils, sourceBean);
        processor100.processor(apacheBeanUtils, sourceBean);

        CopyProcessor processor100R = new CopyProcessor(100);
        processor100R.processor(apacheBeanUtils, sourceBean);
        processor100R.processor(apachePropertyUtils, sourceBean);
        processor100R.processor(springBeanUtils, sourceBean);
        processor100R.processor(cglib, sourceBean);
        processor100R.processor(manualCopy, sourceBean);
    }

    // 测试一千性能测试
    @Test
    public void perform1000() throws Exception {
        CopyProcessor processor1000 = new CopyProcessor(1000);
        processor1000.processor(manualCopy, sourceBean);
        processor1000.processor(cglib, sourceBean);
        processor1000.processor(springBeanUtils, sourceBean);
        processor1000.processor(apachePropertyUtils, sourceBean);
        processor1000.processor(apacheBeanUtils, sourceBean);

        CopyProcessor processor1000R = new CopyProcessor(1000);
        processor1000R.processor(apacheBeanUtils, sourceBean);
        processor1000R.processor(apachePropertyUtils, sourceBean);
        processor1000R.processor(springBeanUtils, sourceBean);
        processor1000R.processor(cglib, sourceBean);
        processor1000R.processor(manualCopy, sourceBean);
    }

    // 测试一万次性能测试
    @Test
    public void perform10000() throws Exception {
        CopyProcessor processor10000 = new CopyProcessor(10000);
        processor10000.processor(manualCopy, sourceBean);
        processor10000.processor(cglib, sourceBean);
        processor10000.processor(springBeanUtils, sourceBean);
        processor10000.processor(apachePropertyUtils, sourceBean);
        processor10000.processor(apacheBeanUtils, sourceBean);

        CopyProcessor processor10000R = new CopyProcessor(10000);
        processor10000R.processor(apacheBeanUtils, sourceBean);
        processor10000R.processor(apachePropertyUtils, sourceBean);
        processor10000R.processor(springBeanUtils, sourceBean);
        processor10000R.processor(cglib, sourceBean);
        processor10000R.processor(manualCopy, sourceBean);
    }

    // 测试十万次性能测试
    @Test
    public void perform100000() throws Exception {
        CopyProcessor processor100000 = new CopyProcessor(100000);
        processor100000.processor(manualCopy, sourceBean);
        processor100000.processor(cglib, sourceBean);
        processor100000.processor(springBeanUtils, sourceBean);
        processor100000.processor(apachePropertyUtils, sourceBean);
        processor100000.processor(apacheBeanUtils, sourceBean);

        processor100000.processor(apacheBeanUtils, sourceBean);
        processor100000.processor(apachePropertyUtils, sourceBean);
        processor100000.processor(springBeanUtils, sourceBean);
        processor100000.processor(cglib, sourceBean);
        processor100000.processor(manualCopy, sourceBean);
    }

    // 测试一百万次性能测试
    @Test
    public void perform1000000() throws Exception {
        CopyProcessor processor1000000 = new CopyProcessor(1000000);
        processor1000000.processor(manualCopy, sourceBean);
        processor1000000.processor(cglib, sourceBean);
        processor1000000.processor(springBeanUtils, sourceBean);
        processor1000000.processor(apachePropertyUtils, sourceBean);
        processor1000000.processor(apacheBeanUtils, sourceBean);

        CopyProcessor processor1000000R = new CopyProcessor(1000000);
        processor1000000R.processor(apacheBeanUtils, sourceBean);
        processor1000000R.processor(apachePropertyUtils, sourceBean);
        processor1000000R.processor(springBeanUtils, sourceBean);
        processor1000000R.processor(cglib, sourceBean);
        processor1000000R.processor(manualCopy, sourceBean);
    }
}
View Code

 

    3、结果比较   

一百次性能测试 第一次(毫秒) 第二次(毫秒) 第三次(毫秒) 每次平均值(毫秒)
manualCopy 0 1 1 0.0066666666666667
cglib 1 2 2 0.0166666666666667
springBeanUtils 177 181 192 1.833333333333333
apachePropertyUtils 179 207 192 1.926666666666667
apacheBeanUtils 96 94 89 0.93

 

一千次性能测试 第一次(毫秒) 第二次(毫秒) 第三次(毫秒) 每次平均值(毫秒)
manualCopy 1 1 2 0.0013333333333333
cglib 13 11 12 0.012
springBeanUtils 272 261 286 0.273
apachePropertyUtils 450 431 444 0.4416666666666667
apacheBeanUtils 349 353 360 0.354

 

一万次性能测试 第一次(毫秒) 第二次(毫秒) 第三次(毫秒) 每次平均值(毫秒)
manualCopy  2  3  4  0.0003
cglib  16  18  17  0.0016
springBeanUtils  526  554  532  0.0537333333333333
apachePropertyUtils  1888  1848  1832  0.1856
apacheBeanUtils  2210  2150  2162  0.2174

 

十万次性能测试 第一次(毫秒) 第二次(毫秒) 第三次(毫秒) 每次平均值(毫秒)
manualCopy  26  24  26  0.00025333
cglib  48  51  48  0.00049
springBeanUtils  1949  1956  1881  0.0192866666666667
apachePropertyUtils  14741  15478  15065  0.1509466666666667
apacheBeanUtils  19506  19800  19753  0.1968633333333333

        输出结果: manualCopy > cglibCopy > springBeanUtils > apachePropertyUtils > apacheBeanUtils 可以理解为: 手工复制 > cglib > 反射。

        对于最求速度的属性拷贝,建议使用手动设置拷贝,虽然代码会变得臃肿不堪。

    4、原理说明

反射类型

都使用静态类调用,最终转化虚拟机中两个单例的工具对象。

public BeanUtilsBean()

{

  this(new ConvertUtilsBean(), new PropertyUtilsBean());

}

ConvertUtilsBean可以通过ConvertUtils全局自定义注册。

ConvertUtils.register(new DateConvert(), java.util.Date.class);

PropertyUtilsBean的copyProperties方法实现了拷贝的算法。

1、  动态bean:orig instanceof DynaBean:Object value = ((DynaBean)orig).get(name);然后把value复制到动态bean类

2、  Map类型:orig instanceof Map:key值逐个拷贝

3、  其他普通类::从beanInfo【每一个对象都有一个缓存的bean信息,包含属性字段等】取出name,然后把sourceClass和targetClass逐个拷贝

 

Cglib类型:BeanCopier

copier = BeanCopier.create(source.getClass(), target.getClass(), false);

copier.copy(source, target, null);

Create对象过程:产生sourceClass-》TargetClass的拷贝代理类,放入jvm中,所以创建的代理类的时候比较耗时。最好保证这个对象的单例模式,可以参照最后一部分的优化方案。

创建过程:源代码见jdk:net.sf.cglib.beans.BeanCopier.Generator.generateClass(ClassVisitor)

1、  获取sourceClass的所有public get 方法-》PropertyDescriptor[] getters

2、  获取TargetClass 的所有 public set 方法-》PropertyDescriptor[] setters

3、  遍历setters的每一个属性,执行4和5

4、  按setters的name生成sourceClass的所有setter方法-》PropertyDescriptor getter【不符合javabean规范的类将会可能出现空指针异常】

5、  PropertyDescriptor[] setters-》PropertyDescriptor setter

6、  将setter和getter名字和类型 配对,生成代理类的拷贝方法。

Copy属性过程:调用生成的代理类,代理类的代码和手工操作的代码很类似,效率非常高。

     Apache BeanUtils.copyProperties会进行类型转换,而Apache PropertyUtils.copyProperties不会。 既然进行了类型转换,那BeanUtils.copyProperties的速度比不上PropertyUtils.copyProperties。我们从上面的实践中得到了验证。

 

三、注意事项                                                                                                

注意事项 是否支持扩展useConvete功能 相同属性名,且类型不匹配时候的处理 Set和Get方法不匹配的处理  对于空字段的处理  
manualCopy  ----  ----  ----  ----  
cglib 支持

只拷贝名称和类型都相同的属性,

名称相同而类型不同的属性不会被拷贝

OK

包装类型未设置值字段,默认设置null
基本数据类型未设置值字段,

默认设置0或0.0

 
springBeanUtils 支持

能正常拷贝并进行初级转换,Long和Integer互转

OK    
apachePropertyUtils 不支持 异常 java.lang.IllegalArgumentException: argument type mismatch OK    
apacheBeanUtils 支持 能正常拷贝并进行初级转换,Long和Integer互转 OK    

 

对于未设置值的field字段,无论是基本数据类型还是包装类型、集合的值都是各自的默认值。

 

四、总结                                                                                                                                                                                                                       

   1、追求高效率的属性拷贝请使用手工设置属性(set)

   2、在使用工具进行属性拷贝时,要注意程序的健壮性即日期Date、各种类型的变量的初始值。

 

参考:http://grefr.iteye.com/blog/1880008

 

由于本人经验有限,文章中难免会有错误,请浏览文章的您指正或有不同的观点共同探讨! 

 

posted @ 2015-10-07 13:20  三石雨  阅读(9022)  评论(0编辑  收藏  举报