一。如何實現不同類型對象之間的復制問題?
1、為什么會有這個問題?
近來在進行一個項目開發的時候,為了隱藏后端數據庫表結構、同時也為了配合給前端一個更友好的API接口文檔(swagger API文檔),我采用POJO來對應數據表結構,使用VO來給傳遞前端要展示的數據,同時使用DTO來進行請求參數的封裝。以上是一個具體的場景,可以發現這樣子一個現象:POJO、VO、DTO對象是同一個數據的不同視圖,所以會有很多相同的字段,由于不同的地方使用不同的對象,無可避免的會存在對象之間的值遷移問題,遷移的一個特征就是需要遷移的值字段相同。字段相同,于是才有了不同對象之間進行值遷移復制的問題。
2、現有的解決方法
一個一個的get出來后又set進去。這個方法無可避免會增加很多的編碼復雜度,還是一些很沒有營養的代碼,看多了還會煩,所以作為一個有點小追求的程序員都沒有辦法忍受這種摧殘。
使用別人已經存在的工具。在spring包里面有一個可以復制對象屬性的工具方法,可以進行對象值的復制,下一段我們詳細去分析它的這個工具方法。
自己動手豐衣足食。自己造工具來用,之所以自己造工具不是因為喜歡造工具,而是現有的工具沒辦法解決自己的需求,不得已而為之。
二、他山之石可以攻玉,詳談spring的對象復制工具
1、看看spring的對象復制工具到底咋樣?
類名:org.springframework.beans.BeanUtils
這個類里面所有的屬性復制的方法都調用了同一個方法,我們就直接分析這個原始的方法就行了。
/**
* Copy the property values of the given source bean into the given target bean.
*
Note: The source and target classes do not have to match or even be derived
* from each other, as long as the properties match. Any bean properties that the
* source bean exposes but the target bean does not will silently be ignored.
* @param source the source bean:也就是說要從這個對象里面復制值出去
* @param target the target bean:出去就是復制到這里面來
* @param editable the class (or interface) to restrict property setting to:這個類對象是target的父類或其實現的接口,用于控制屬性復制的范圍
* @param ignoreProperties array of property names to ignore:需要忽略的字段
* @throws BeansException if the copying failed
* @see BeanWrapper
*/
private static void copyProperties(Object source, Object target, Class> editable, String... ignoreProperties)
throws BeansException {
//這里在校驗要復制的對象是不可以為null的,這兩個方法可是會報錯的!!
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
//這里和下面的代碼就有意思了
Class> actualEditable = target.getClass();//獲取目標對象的動態類型
//下面判斷的意圖在于控制屬性復制的范圍
if (editable != null) {
//必須是target對象的父類或者其實現的接口類型,相當于instanceof運算符
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
//不得不說,下面這段代碼乖巧的像綿羊,待我們來分析分析它是如何如何乖巧的
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);//獲取屬性描述,描述是什么?描述就是對屬性的方法信息的封裝,好乖。
List ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
//重頭戲開始了!開始進行復制了
for (PropertyDescriptor targetPd : targetPds) {
//先判斷有沒有寫方法,沒有寫方法我也就沒有必要讀屬性出來了,這個懶偷的真好!
Method writeMethod = targetPd.getWriteMethod();
//首先,沒有寫方法的字段我不寫,乖巧撒?就是說你不讓我改我就不改,讓我忽略我就忽略!
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
//如果沒辦法從原對象里面讀出屬性也沒有必要繼續了
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
//這里就更乖巧了!寫方法不讓我寫我也不寫!!!
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
//這里就算了,來都來了,就乖乖地進行值復制吧,別搞東搞西的了
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
2、對復制工具的一些看法和總結
總結上一段代碼的分析,我們發現spring自帶的工具有以下特點:
它名副其實的是在復制屬性,而不是字段!!
它可以通過一個目標對象的父類或者其實現的接口來控制需要復制屬性的范圍
很貼心的可以忽略原對象的某些字段,可以通過2的方法忽略某些目標對象的字段
但是,這遠遠不夠!!!我需要如下的功能:
復制對象的字段,而不是屬性,也就是說我需要一個更暴力的復制工具。
我需要忽略原對象的某些字段,同時也能夠忽略目標對象的某些字段。
我的項目還需要忽略原對象為null的字段和目標對象不為null的字段
帶著這三個需求,開始我的工具制造。