功能簡介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)安全
都使用靜態類調用,最終轉化虛擬機中兩個單例的工具對象。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逐個拷貝
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方法) |
對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; } 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)); } } |
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則忽略掉該字段的操做,這樣就能夠避免異常的發生。 |