天天看點

對象拷貝類PropertyUtils,BeanUtils,BeanCopier的技術沉澱對象拷貝的應用現狀簡介: 目前流行的較為公用認可的工具類: 原理簡介你不知道這些陷阱吧? 一些優化和改進

性能對比: beancopier > propertyutils > beanutils. 其中beancopier的性能高出另外兩個100數量級。

業務系統中經常需要兩個對象進行屬性的拷貝,不能否認逐個的對象拷貝是最快速最安全的做法,但是當資料對象的屬性字段數量超過程式員的容忍的程度,代碼是以變得臃腫不堪,使用一些友善的對象拷貝工具類将是很好的選擇。

apache的兩個版本:(反射機制)

org.apache.commons.beanutils.propertyutils.copyproperties(object dest, object orig)

org.apache.commons.beanutils.beanutils.copyproperties(object dest, object orig)

spring版本:(反射機制)

org.springframework.beans.beanutils.copyproperties(object source, object target, class editable, string[] ignoreproperties)

cglib版本:(使用動态代理,效率高)

net.sf.cglib.beans.beancopier.copy(object paramobject1, object paramobject2, converter paramconverter)

都使用靜态類調用,最終轉化虛拟機中兩個單例的工具對象。

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逐個拷貝

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屬性過程:調用生成的代理類,代理類的代碼和手工操作的代碼很類似,效率非常高。

<a href="http://blog.csdn.net/express_wind/article/details/7326359">缺陷預防</a>

陷阱條件

apache- propertyutils

apache- beanutils

spring-  beanutils

cglib-

beancopier

是否可以擴充

useconvete功能

no

yes

yes,但比較難用

(sourceobject,targetobject)的順序

逆序

ok

對sourceobject特殊屬性的限制:(date,bigdecimal等)【見備注1】

no,異常出錯

相同屬性名,且類型不比對時候的處理

【見備注2】

異常,拷貝部分屬性,非常危險

ok,并能進行初級轉換,long和integer互轉

異常,拷貝部分屬性

ok,但是該屬性不拷貝

get和set方法不比對的處理

【見備注3】

建立拷貝的時候報錯,無法拷貝任何屬性(當且僅當sourceclass的get方法超過set方法)

對targetobject特殊屬性的限制:(date,bigdecimal等)

原因:datetimeconveter的conveter沒有對null值的處理

public class errorbeanutilobject { //此處省略getter,setter方法

    private string name;

    private java.util.date date;

 public class errorbeanutilstest {  

    public static void main(string args[]) throws throwable  {  

    errorbeanutilobject from = new errorbeanutilobject(); 

    errorbeanutilobject to = new errorbeanutilobject();  

    //from.setdate(new java.util.date());

    from.setname("tttt");

    org.apache.commons.beanutils.beanutils.copyproperties(to, from);//如果from.setdate去掉,此處出現conveter異常

    system.out.println(tostringbuilder.reflectiontostring(from));

    system.out.println(tostringbuilder.reflectiontostring(to));

    }  

原因:這兩個工具類不支援同名異類型的比對 !!!【包裝類long和原始資料類型long是可以的】

public class targetclass {  //此處省略getter,setter方法

    private long num;  

    private string name;

    private long num;

public class errorpropertyutilstest {        

    public static void main(string args[]) throws illegalaccessexception, invocationtargetexception, nosuchmethodexception  {  

        sourceclass from = new sourceclass();  

        from.setnum(1);

        from.setname("name"); 

        targetclass to = new targetclass();  

        org.apache.commons.beanutils.propertyutils.copyproperties(to, from); //抛出參數不比對異常

        org.springframework.beans.beanutils.copyproperties(from, to);

//抛出參數不比對異常

        system.out.println(tostringbuilder.reflectiontostring(from));    

        system.out.println(tostringbuilder.reflectiontostring(to));  

public class errorbeancopiertest {    

    /**

     * 從該用例看出beancopier.create的target.class 的每一個get方法必須有隊形的set方法

     * @param args

     */

    public static void main(string args[]) {  

        beancopier copier = beancopier.create(unsatifisedbeancopierobject.class, sourceclass.class,false);

        copier = beancopier.create(sourceclass.class, unsatifisedbeancopierobject.class, false); //此處抛出異常建立 

class unsatifisedbeancopierobject {   

    public string getname() {

       return name;

    }

    public void setname(string name) {

       this.name = name;

    public long getnum() {

       return num;

//  public void setnum(long num) {

//     this.num = num;

//  }

<a href="http://blog.csdn.net/express_wind/article/details/7326369">優化方案</a>

public class beanutilsex extends beanutils

  public static void copyproperties(object dest, object orig)

  {

    try

    {

      beanutils.copyproperties(dest, orig);

    } catch (illegalaccessexception ex) {

      ex.printstacktrace();

    } catch (invocationtargetexception ex) {

  }

  static

    convertutils.register(new dateconvert(), java.util.date.class);

    convertutils.register(new dateconvert(), java.sql.date.class);

    convertutils.register(new bigdecimalconvert(), bigdecimal.class);

将beancopier做成靜态類,友善拷貝

public class beancopierutils {

     public static map&lt;string,beancopier&gt; beancopiermap = new hashmap&lt;string,beancopier&gt;();

     public static void copyproperties(object source, object target){

         string beankey =  generatekey(source.getclass(), target.getclass());

         beancopier copier =  null;

         if(!beancopiermap.containskey(beankey)){

              copier = beancopier.create(source.getclass(), target.getclass(), false);

              beancopiermap.put(beankey, copier);

         }else{

              copier = beancopiermap.get(beankey);

         }

         copier.copy(source, target, null);

     }   

     private static string generatekey(class&lt;?&gt; class1,class&lt;?&gt;class2){

         return class1.tostring() + class2.tostring();

     }

修複beancopier對set方法強限制的限制

改寫net.sf.cglib.beans.beancopier.generator.generateclass(classvisitor)方法

将133行的

methodinfo write = reflectutils.getmethodinfo(setter.getwritemethod());

預先存一個names2放入

/* 109 */       map names2 = new hashmap();

/* 110 */       for (int i = 0; i &lt; getters.length; ++i) {

/* 111 */         names2.put(setters[i].getname(), getters[i]);

/*     */       }

調用這行代碼前判斷查詢下,如果沒有改writemethod則忽略掉該字段的操作,這樣就可以避免異常的發生。

特别說明:尊重作者的勞動成果,轉載請注明出處哦~~~http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp37