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

本文对比分析了五种属性拷贝方法(手动拷贝、cglib、Spring BeanUtils、Apache PropertyUtils、Apache BeanUtils)在不同拷贝次数下的性能表现,并讨论了它们的工作原理和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 一、对象属性拷贝工具类                                                                                 

    ”天下武功,唯快不破“。在互联网行业中体现的更加淋淋尽致。我们在业务系统会经常遇到业务对象间属性的拷贝,对如外接口一般都使用特定的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、结果比较   

一百次性能测试第一次(毫秒)第二次(毫秒)第三次(毫秒)每次平均值(毫秒)
manualCopy0110.0066666666666667
cglib1220.0166666666666667
springBeanUtils1771811921.833333333333333
apachePropertyUtils1792071921.926666666666667
apacheBeanUtils9694890.93

 

一千次性能测试第一次(毫秒)第二次(毫秒)第三次(毫秒)每次平均值(毫秒)
manualCopy1120.0013333333333333
cglib1311120.012
springBeanUtils2722612860.273
apachePropertyUtils4504314440.4416666666666667
apacheBeanUtils3493533600.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 mismatchOK  
apacheBeanUtils支持能正常拷贝并进行初级转换,Long和Integer互转OK  

 

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

 

四、总结                                                                                                                                                                                                                       

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

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

 

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

 

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

 

转载于:https://www.cnblogs.com/exceptioneye/p/4852962.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值