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

功能簡介java

 

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

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

目前流行的較爲公用承認的工具類:

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

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

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

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

org.springframework.beans.BeanUtils.copyProperties(Object source, Object target, Class editable, String[] ignoreProperties)apache

cglib版本:(使用動態代理,效率高)緩存

net.sf.cglib.beans.BeanCopier.copy(Object paramObject1, Object paramObject2, Converter paramConverter)安全

原理簡介

反射類型:(apache

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

public BeanUtilsBean()

{

  this(new ConvertUtilsBean(), new PropertyUtilsBean());

}

ConvertUtilsBean能夠經過ConvertUtils全局自定義註冊。

ConvertUtils.register(new DateConvert(), java.util.Date.class);

PropertyUtilsBean的copyProperties方法實現了拷貝的算法。

一、  動態bean:orig instanceof DynaBean:Object value = ((DynaBean)orig).get(name);而後把value複製到動態bean類

二、  Map類型:orig instanceof Map:key值逐個拷貝

三、  其餘普通類::從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)

一、  獲取sourceClass的全部public get 方法-》PropertyDescriptor[] getters

二、  獲取TargetClass 的全部 public set 方法-》PropertyDescriptor[] setters

三、  遍歷setters的每個屬性,執行4和5

四、  按setters的name生成sourceClass的全部setter方法-》PropertyDescriptor getter【不符合javabean規範的類將會可能出現空指針異常】

五、  PropertyDescriptor[] setters-》PropertyDescriptor setter

六、  將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則忽略掉該字段的操做,這樣就能夠避免異常的發生。

相關文章
相關標籤/搜索