一、对象属性拷贝工具类                                                                                 

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

      

java 拷贝对象 不影响原对象 java属性拷贝工具_spring

    2、代码结构

      

java 拷贝对象 不影响原对象 java属性拷贝工具_System_02




java 拷贝对象 不影响原对象 java属性拷贝工具_spring_03

java 拷贝对象 不影响原对象 java属性拷贝工具_java 拷贝对象 不影响原对象_04

package test;

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

    public DestBean callBack(SourceBean sourceBean) throws Exception;
}


View Code



java 拷贝对象 不影响原对象 java属性拷贝工具_spring_03

java 拷贝对象 不影响原对象 java属性拷贝工具_java 拷贝对象 不影响原对象_04

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



java 拷贝对象 不影响原对象 java属性拷贝工具_spring_03

java 拷贝对象 不影响原对象 java属性拷贝工具_java 拷贝对象 不影响原对象_04

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

 

 

 

java 拷贝对象 不影响原对象 java属性拷贝工具_spring_09

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

 

四、总结                                                                                                    

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

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