1. 程式人生 > >物件拷貝類PropertyUtils,BeanUtils,BeanCopier的技術沉澱

物件拷貝類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版本:(反射機制)

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)

原理簡介

都使用靜態類呼叫,最終轉化虛擬機器中兩個單例的工具物件。

publicBeanUtilsBean()

{

 this(newConvertUtilsBean(),newPropertyUtilsBean());

}

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 classErrorBeanUtilObject {//此處省略getter,setter方法

   privateStringname;

   privatejava.util.Datedate;

}

 public classErrorBeanUtilsTest {  

   public static voidmain(String args[])throwsThrowable  {  

    ErrorBeanUtilObject from =newErrorBeanUtilObject(); 

    ErrorBeanUtilObject to =newErrorBeanUtilObject();  

    //from.setDate(newjava.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 classTargetClass { //此處省略getter,setter方法

   privateLong num;  

   privateString name;

}

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

   privateLong num;

   privateString name;

}

public classErrorPropertyUtilsTest {        

   public static voidmain(String args[])throwsIllegalAccessException, InvocationTargetException, NoSuchMethodException  {  

        SourceClass from =newSourceClass();  

        from.setNum(1);

        from.setName("name"); 

        TargetClass to =newTargetClass();  

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

    /**

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

     *@paramargs

     */

   public static voidmain(String args[]) {  

        BeanCopier copier = BeanCopier.create(UnSatifisedBeanCopierObject.class, SourceClass.class,false);

        copier = BeanCopier.create(SourceClass.class, UnSatifisedBeanCopierObject.class,false); //此處丟擲異常建立 

    }  

}

classUnSatifisedBeanCopierObject {   

   privateString name;

   privateLong num;

   publicString getName() {

      returnname;

    }

   public voidsetName(String name) {

      this.name = name;

    }

   publicLong getNum() {

      returnnum;

    }

//  public void setNum(Longnum) {

//     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 classBeanCopierUtils {

    public staticMap<String,BeanCopier>beanCopierMap=newHashMap<String,BeanCopier>();

    public static voidcopyProperties(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 staticString generateKey(Class<?> class1,Class<?>class2){

        returnclass1.toString() + class2.toString();

     }

}

修復beanCopier對set方法強限制的約束

改寫net.sf.cglib.beans.BeanCopier.Generator.generateClass(ClassVisitor)方法

將133行的

MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());

預先存一個names2放入

/* 109 */       Map names2 =newHashMap();

/* 110 */      for(inti = 0; i < getters.length; ++i) {

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

/*     */       }

呼叫這行程式碼前判斷查詢下,如果沒有改writeMethod則忽略掉該欄位的操作,這樣就可以避免異常的發生。

相關推薦

物件貝類PropertyUtilsBeanUtilsBeanCopier技術沉澱

功能簡介 業務系統中經常需要兩個物件進行屬性的拷貝,不能否認逐個的物件拷貝是最快速最安全的做法,但是當資料物件的屬性欄位數量超過程式設計師的容忍的程度,程式碼因此變得臃腫不堪,使用一些方便的物件拷貝工具類將是很好的選擇。 Apache的兩個版本:(反射機制)

對象貝類PropertyUtilsBeanUtilsBeanCopier技術沈澱

clas mco lsb spring visitor 單例 style mon trace 功能簡介 對象拷貝的應用現狀簡介: 業務系統中經常需要兩個對象進行屬性的拷貝,不能否認逐個的對象拷貝是最快速最安全的做法,但是當數據對象的屬性字段數量超過程序員的容忍的程度,代

Bean複製的幾種框架效能比較(Apache BeanUtilsPropertyUtils,Spring BeanUtils,Cglib BeanCopier

 進行了三次測試,最後的結果如下: 10次測驗 第一次 第二次 第三次 平均值 每次平均值 BeanUtil.copyProperties 54 57 50 53.66667 5.366666667 PropertyUtils.copyProperties 4 4 4 4

黑馬程式設計師------java中的反射beanutils註解的應用。

Class類:描述眾多java類的物件。代表記憶體裡的一份位元組碼。 有三種方式可以獲取一個類的Class檔案。 方法一:是通過該類物件.getClass()方法。 方法二:通過Class類的靜態方法,Class.forName("name"); 方法三:

sping,springMVC @Component 註解的物件都是單例模式變數不能全域性

錯誤方式:      將屬性和變數定義為全域性,單例模式,所有人共享,導致所有人的資料都發生錯誤!   正確方式 一:    將變數定義到區域性,互不影響。   正確方式 二:      

JS建立物件陣列函式的三種方式

    害怕自己忘記,簡單總結一下     建立物件的3種方法   ①:建立一個空物件      var obj = {};   ②:物件字面量     var obj = {       name: "Tom",       age: 27    }   ③

工作流4-流程例項任務執行物件控制流程的執行

流程例項: 從開始到結束 流程物件: 一個流程,流程例項只有一個,執行物件可以存在多個 1.啟動流程例項 public void startProcessInstance(){ //流程定義的key,根據key啟動最新version流程 String pr

SpringMVC傳參——物件字串陣列

PageResult.class public class PagedResult<T> { private List<T> dataList;//資料 private int currentPage;//當前頁 private int pageSize

WPFS資料繫結(要是後臺類物件的屬性值發生改變通知在“客戶端介面與之繫結的控制元件值”也發生改變需要實現INotitypropertyChanged介面)

WPFS資料繫結(要是後臺類物件的屬性值發生改變,通知在“客戶端介面與之繫結的控制元件值”也發生改變需要實現INotitypropertyChanged介面) MainWindow.xaml 1 <Window x:Class="WpfApplication1.MainWindow" 2

week6:面向物件之成員修飾符特殊成員異常處理發射單例

一、成員修飾符 共有成員 私有成員, __欄位名  - 無法直接訪問,只能間接訪問          class Foo: def __init__(self, name, age): self.name

字串陣列數值物件的擴充套件

//字串 1. includes(str) : 判斷是否包含指定的字串 2. startsWith(str) : 判斷是否以指定字串開頭 3. endsWith(str) : 判斷是否以指定字串結尾 4. repeat(count) : 重複指定次數 //數值 1. 二進位制與八進位制數值表示法:

06 ListenerFilterBeanUtils

Listener監聽器,監聽某一個事件的發生。 狀態的改變。內部機制其實就是介面回撥. 介面回撥 需求:A在執行迴圈,當迴圈到5的時候, 通知B。事先先把一個物件傳遞給 A , 當A 執行到5的時候,通過這個物件,來呼叫B中的方法。 但是注意,不是直接傳遞B的例項,而是傳遞一個介面的例項過去。Web

js獲取物件屬性的兩種方法object.屬性名[‘屬性名’ ]

1、通過點的方式 2、通過括號的方式 例: <input type="text" value="hello" id="text"/> var oText = document.getElementById("text") (1)通過點的方式   oText.pr

js---聖盃模式 列舉如何區分陣列和物件callee

1. 繼承發展史(從a發展到d) a 原型鏈繼承:過多的繼承沒有用的屬性 function Grand(){this.grand='grand';this.name='haha'} function Father(){this.father='father'} function Son(){th

redis 系列9 物件型別(字串雜湊列表集合有序集合)與資料結構關係

原文: redis 系列9 物件型別(字串,雜湊,列表,集合,有序集合)與資料結構關係 一.概述   在前面章節中,主要了解了 Redis用到的主要資料結構,包括:簡單動態字串、連結串列(雙端連結串列)、字典、跳躍表、 整數集合、壓縮列表(後面再瞭解)。Redis沒有直接使用這些資料結構來實現鍵

JavaScript中prototype(原型)給字串物件新增一個toCharArray的方法reverse(翻轉)的 方法

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http:/

JS 語法之--物件型別(構造器classthis)高階物件(高階類Minix模式)

1、JS 物件型別   JavaScript 是一種基於原型(prototype)的面嚮物件語言,而不是基於類的面嚮物件語言   C++, Java 有類Class 和例項Instance 的概念,類是一類事物 的抽象,而例項則是類的實體。   JS是基於原型的語言,它只有原型物件的概念,原型物件就是一

初步類與物件封裝構造方法

一,理解面對物件的思想 物件是已知的事物 物件會執行的動作 物件本身已知的事物被稱為 實際變數(instance variable) 物件可以執行的動作成為 方法(methods) 就是說: 如果你如果想洗衣服的話,面向過程就是你需要執行一系類的過程,裝衣服,放盆子裡,到洗衣

JavaScript基本資料型別函式物件陣列字串函式呼叫

  直接上程式碼了, 1 cc.Class({ 2 extends: cc.Component, 3 4 properties: { 5 6 }, 7 //JS基本資料_函式物件_表_陣列_字串_函式呼叫 8 onLoad:funct

面向物件繼承多型封裝1

---恢復內容開始--- 繼承:       一個類可以被多個類繼承,一個類也可以有多個父類,父類裡面的方法屬性子類都可以用  1.單繼承      class Alimone: #父類 def __init__