對象拷貝類PropertyUtils,BeanUtils,BeanCopier的技術沉淀


功能簡介

 

對象拷貝的應用現狀簡介:

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

目前流行的較為公用認可的工具類:

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

org.apache.commons.beanutils.PropertyUtils.copyProperties(Object dest, Object orig)

org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig)

Spring版本:(反射機制)  對應的包為:spring-beans-4.0.6.RELEASE.jar

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)

原理簡介

反射類型:(apache

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

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- PropertyUtils

Apache- BeanUtils

Spring-  BeanUtils

Cglib-

BeanCopier

是否可以擴展

useConvete功能

NO

Yes

Yes

Yes,但比較難用

(sourceObject,targetObject)的順序

逆序

逆序

OK

 

OK

對sourceObject特殊屬性的限制:(Date,BigDecimal等)【見備注1】

OK

NO,異常出錯

OK

OK

相同屬性名,且類型不匹配時候的處理

【見備注2】

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

OK,並能進行初級轉換,Long和Integer互轉

異常,拷貝部分屬性

OK,但是該屬性不拷貝

Get和set方法不匹配的處理

【見備注3】

OK

OK

OK

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

 

 

備注1

 

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

    }  

}

 

 

備注2

 

相同屬性名,且類型不匹配時候的處理

原因:這兩個工具類不支持同名異類型的匹配 !!!【包裝類Long和原始數據類型long是可以的】

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

    private Long num;  

    private String name;

}

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

    private Long num;

    private String name;

}

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

    }  

}

 

 

備注3

 

Get和set方法不匹配的處理

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 {   

    private String name;

    private Long num;

    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;

//  }

}

 

 

 

 

優化方案

一些優化和改進

增強apache的beanUtils的拷貝屬性,注冊一些新的類型轉換

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) {

      ex.printStackTrace();

    }

  }

  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<String,BeanCopier> beanCopierMap = new HashMap<String,BeanCopier>();

    

     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<?> class1,Class<?>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 < getters.length; ++i) {

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

/*     */       }

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

 


免責聲明!

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



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