利用Java反射機制實現對象相同字段的複製

一。如何實現不同類型對象之間的複製問題?

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.
 * <p>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<String> 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的字段
帶着這三個需求,開始我的工具製造。


作者:樹樹在變幹
來源:CSDN

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章