物件拷貝類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則忽略掉該欄位的操作,這樣就可以避免異常的發生。 |
相關推薦
物件拷貝類PropertyUtils,BeanUtils,BeanCopier的技術沉澱
功能簡介 業務系統中經常需要兩個物件進行屬性的拷貝,不能否認逐個的物件拷貝是最快速最安全的做法,但是當資料物件的屬性欄位數量超過程式設計師的容忍的程度,程式碼因此變得臃腫不堪,使用一些方便的物件拷貝工具類將是很好的選擇。 Apache的兩個版本:(反射機制)
對象拷貝類PropertyUtils,BeanUtils,BeanCopier的技術沈澱
clas mco lsb spring visitor 單例 style mon trace 功能簡介 對象拷貝的應用現狀簡介: 業務系統中經常需要兩個對象進行屬性的拷貝,不能否認逐個的對象拷貝是最快速最安全的做法,但是當數據對象的屬性字段數量超過程序員的容忍的程度,代
Bean複製的幾種框架效能比較(Apache BeanUtils、PropertyUtils,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 Listener,Filter,BeanUtils
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 語法之--物件型別(構造器,class,this),高階物件(高階類,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__