對(duì)象拷貝現(xiàn)狀
業(yè)務(wù)系統(tǒng)中經(jīng)常需要兩個(gè)對(duì)象進(jìn)行屬性的拷貝,不能否認(rèn)逐個(gè)的對(duì)象拷貝是最快速最安全的做法,但是當(dāng)數(shù)據(jù)對(duì)象的屬性字段數(shù)量超過程序員的容忍的程度,代碼因此變得臃腫不堪,使用一些方便的對(duì)象拷貝工具類將是很好的選擇。
模型數(shù)據(jù)轉(zhuǎn)換
項(xiàng)目中或多或少會(huì)對(duì)某些實(shí)體進(jìn)行轉(zhuǎn)換(DTO、VO、DO 或者 PO 等),往往具有相同的屬性名稱,數(shù)量少的情況下我們可以直接采取 set、get 方法進(jìn)行賦值,可是如果這樣的轉(zhuǎn)換在很多地方都會(huì)用到,還是靠 set 來進(jìn)行操作勢必會(huì)大大的影響開發(fā)效率。
- 關(guān)于實(shí)體轉(zhuǎn)換,我們把一個(gè)實(shí)體對(duì)應(yīng)一張表(這可以當(dāng)成 DO)。
- 業(yè)務(wù)中與第三方進(jìn)行數(shù)據(jù)交互,我們需要把實(shí)體的數(shù)據(jù)傳給他們,但不一定是一個(gè) DO 中的所有屬性可能減少或者多個(gè) DO 中的屬性組成,這里我們引入 DTO(這個(gè)實(shí)體中我們可以去除一些隱私信息,比如:銀行卡號(hào),身份證,密碼)。
- 一個(gè)性別我們用 1、2 表示男女,頁面中不能直接顯示 1 或者 2,需要顯示男、女或者靚仔(男)、靚妹(女),這時(shí)候代表這樣的一個(gè)實(shí)體我們可以看作 VO。
目前流行的較為公用認(rèn)可的工具類:
Apache 的兩個(gè)版本:(反射機(jī)制)
- org.apache.commons.beanutils.PropertyUtils.copyProperties(Object dest, Object orig)
原因:dateTimeConveter 的 conveter 沒有對(duì) null 值的處理
// targetObject特殊屬性的限制:(Date,BigDecimal等)
public class BeanObject { //此處省略getter,setter方法
private String name;
private java.util.Date date;
}
public class BeanObjectTest {
public static void main(String args[]) throws Throwable {
BeanObject from = new BeanObject();
BeanObject to = new BeanObject();
//from.setDate(new java.util.Date());
from.setName("TTTT");
org.apache.commons.beanutils.BeanUtils.copyProperties(to, from);//如果from.setDate去掉,此處出現(xiàn)conveter異常
System.out.println(ToStringBuilder.reflectionToString(from));
System.out.println(ToStringBuilder.reflectionToString(to));
}
}
- org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig)
- 相同屬性名,且類型不匹配時(shí)候的處理
- 原因:這兩個(gè)工具類不支持同名異類型的匹配 !!!【包裝類 Long 和原始數(shù)據(jù)類型 long 是可以的】
public class SourceClass { //此處省略getter,setter方法
private Long num;
private String name;
}
public class TargetClass { //此處省略getter,setter方法
private Long num;
private String name;
}
public class PropertyUtilsTest {
public static void main(String args[]) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
SourceClass from = new SourceClass();
from.setNum(1);
from.setName("name");
TargetClass to = new TargetClass();
//拋出參數(shù)不匹配異常
org.apache.commons.beanutils.PropertyUtils.copyProperties(to, from);
org.springframework.beans.BeanUtils.copyProperties(from, to);
//拋出參數(shù)不匹配異常
System.out.println(ToStringBuilder.reflectionToString(from));
System.out.println(ToStringBuilder.reflectionToString(to));
}
}
Spring 版本:(反射機(jī)制)
- org.springframework.beans.BeanUtils.copyProperties(Object source, Object target, Class editable, String[] ignoreProperties)
cglib 版本:(使用動(dòng)態(tài)代理,效率高)
cglib 是一款比較底層的操作 java 字節(jié)碼的框架
- net.sf.cglib.beans.BeanCopier.copy(Object paramObject1, Object paramObject2, Converter paramConverter)
工具操作

原理簡介
反射類型:(apache)
都使用靜態(tài)類調(diào)用,最終轉(zhuǎn)化虛擬機(jī)中兩個(gè)單例的工具對(duì)象。
public BeanUtilsBean(){
this(new ConvertUtilsBean(), new PropertyUtilsBean());
}
- ConvertUtilsBean 可以通過 ConvertUtils 全局自定義注冊(cè)。
- ConvertUtils.register(new DateConvert(), java.util.Date.class);
- PropertyUtilsBean 的 copyProperties 方法實(shí)現(xiàn)了拷貝的算法。
動(dòng)態(tài) bean:orig instanceof DynaBean:Object value = ((DynaBean)orig).get(name); 然后把 value 復(fù)制到動(dòng)態(tài) bean 類。
Map 類型:orig instanceof Map:key 值逐個(gè)拷貝
其他普通類:從 beanInfo【每一個(gè)對(duì)象都有一個(gè)緩存的 bean 信息,包含屬性字段等】取出 name,然后把 sourceClass 和 targetClass 逐個(gè)拷貝。
Cglib 類型:BeanCopier
copier = BeanCopier.create(source.getClass(), target.getClass(), false);
copier.copy(source, target, null);
Get 和 set 方法不匹配的處理
public class BeanCopierTest {
/**
* 從該用例看出BeanCopier.create的target.class 的每一個(gè)get方法必須有隊(duì)形的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); //此處拋出異常創(chuàng)建
}
}
class UnSatifisedBeanCopierObject {
private String name;
private Long num;
public String getName() {undefined
return name;
}
public void setName(String name) {undefined
this.name = name;
}
public Long getNum() {undefined
return num;
}
// public void setNum(Long num) {undefined
// this.num = num;
// }
}
Create 對(duì)象過程:產(chǎn)生 sourceClass-> TargetClass 的拷貝代理類,放入 jvm 中,所以創(chuàng)建的代理類的時(shí)候比較耗時(shí)。最好保證這個(gè)對(duì)象的單例模式,可以參照最后一部分的優(yōu)化方案。
創(chuàng)建過程 -> 源代碼見 jdk:
net.sf.cglib.beans.BeanCopier.Generator.generateClass(ClassVisitor)
- 獲取 sourceClass 的所有 public get 方法-》PropertyDescriptor[] getters
- 獲取 TargetClass 的所有 public set 方法-》PropertyDescriptor[] setters
- 遍歷 setters 的每一個(gè)屬性,執(zhí)行 4 和 5
- 按 setters 的 name 生成 sourceClass 的所有 setter 方法-》PropertyDescriptor getter【不符合 javabean 規(guī)范的類將會(huì)可能出現(xiàn)空指針異?!?/li>
- PropertyDescriptor[] setters-》PropertyDescriptor setter
- 將 setter 和 getter 名字和類型 配對(duì),生成代理類的拷貝方法。
原理總結(jié)
Copy 屬性過程:調(diào)用生成的代理類,代理類的代碼和手工操作的代碼很類似,效率非常高。
上述這幾種方式速度最快的是 BeanCopier,默認(rèn)只復(fù)制名稱和類型相同的字段,還會(huì)對(duì) date 為空的情況不進(jìn)行復(fù)制。
我認(rèn)為這樣做最好,比如對(duì)象 A 的值復(fù)制到 B 中,我們把相同的進(jìn)行復(fù)制,把不同的,也就是需要我們個(gè)性化的一些字段,單獨(dú)出來用 get 來賦值,這樣程序就會(huì)很明確,重點(diǎn)也就聚焦在了不同的地方。