1. 程式人生 > >對象拷貝類PropertyUtils,BeanUtils,BeanCopier的技術沈澱

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

clas mco lsb spring visitor 單例 style mon trace

功能簡介

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

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

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

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)

原理簡介

反射類型:(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則忽略掉該字段的操作,這樣就可以避免異常的發生。

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