文档章节

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

haoran_10
 haoran_10
发布于 2017/04/06 12:19
字数 2001
阅读 26
收藏 0

 一、对象属性拷贝工具类                                                                                 

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

复制代码

复制代码

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("----------------------------------------");
    }

}

复制代码

复制代码

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);
    }
}

复制代码

 

    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://www.cnblogs.com/exceptioneye/p/4852962.html

共有 人打赏支持
haoran_10
粉丝 25
博文 88
码字总数 80846
作品 0
杭州
程序员
私信 提问
编写高质量代码:改善Java程序的151个建议 --[36~51]

编写高质量代码:改善Java程序的151个建议 --[36~51] 工具类不可实例化 工具类的方法和属性都是静态的,不需要生成实例即可访 问,而且JDK也做了很好的处理,由于不希望被初始化,于是就设置了...

西北野狼
08/06
0
0
Java拾遗:008 - 对象克隆与浅拷贝、深拷贝

对象克隆 Object类中有一个方法叫,完整代码 首先它是一个Native方法,而且是受保护的(),抛出一个异常(JDK1.8)。 通常程序员自己定义的类不能直接调用方法,如果要在外部调用,需要重写...

一别丶经年
08/04
0
0
YYModel 源码剖析:关注性能

前言 json与模型的转换框架很多,YYModel 一出,性能吊打同类组件,终于找了些时间观摩了一番,确实收益颇多,写下此文作为分享。 由于该框架代码比较多,考虑到突出重点,压缩篇幅,不会有太...

indulge_in
06/16
0
0
java中Final关键字和Immutable Class以及Guava的不可变对象

大纲 写这篇文章的起因 java中Final关键字 如何构建不可变对象 Guava中不可变对象和Collections工具类的unmodifiableSet/List/Map/etc的区别 实验代码 写这篇文章的起因 java项目在使用FindB...

敲代码猥琐男
2015/09/23
1K
0
commons-beanutils

概述 commons-beanutil开源库是apache组织的一个基础的开源库,为apache中许多类提供工具方法,学习它是学习其他开源库实现的基础。 Commons-beanutil中包含大量和JavaBean操作有关的工具方法...

wuchongchang
2011/09/15
0
0

没有更多内容

加载失败,请刷新页面

加载更多

错误: 找不到或无法加载主类

在IDEA的使用过程中,经常断掉服务或者重启服务,最近断掉服务重启时突然遇到了一个启动报错: 错误:找不到或无法加载主类 猜测:1,未能成功编译; 尝试:菜单---》Build---》Rebuild Pro...

安小乐
16分钟前
1
0
vue路由传参,刷新页面,引发的bug

最近遇到一个bug 通过vue路由跳转到页面, 然后接参控制(v-if ),成功显示 而刷新页面,显示失败。 苦苦地找了半天原因,打印参数发现正确,再打印下类型......,路由跳过来会保持传参时的...

hanbb
17分钟前
0
0
【58沈剑 架构师之路】InnoDB,select为啥会阻塞insert?

MySQL的InnoDB的细粒度行锁,是它最吸引人的特性之一。 但是,如《InnoDB,5项最佳实践》所述,如果查询没有命中索引,也将退化为表锁。 InnoDB的细粒度锁,是实现在索引记录上的。 一,Inn...

张锦飞
20分钟前
0
0
冒泡,选择和插入排序比较

/** * 冒泡排序,两层嵌套循环,内层局部比较后,找出最大或者最小数据并交换数据,使其局部有序,外层用于比较剩余元素,相较于选择排序,选择排序相当于是冒泡的一个优化版本(减少了交换...

strict_nerd
21分钟前
0
0
html内联(行内)元素、块级(块状)元素和行内块元素分类

HTML可以将元素分类方式分为内联(行内)元素、块级(块状)元素和行内块元素三种。 注:HTML是标签语言,那么既然是标签,就可以自己定义一些自己元素(如<wode>自定义的元素</wode>等),自...

NB-One
27分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部