對象屬性拷貝工具類的性能比較


 一、對象屬性拷貝工具類                                                                                 

    ”天下武功,唯快不破“。在互聯網行業中體現的更加淋淋盡致。我們在業務系統會經常遇到業務對象間屬性的拷貝,對如外接口一般都使用特定的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

 

由於本人經驗有限,文章中難免會有錯誤,請瀏覽文章的您指正或有不同的觀點共同探討! 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM