淺析BeanUtils

淺析BeanUtils

在聊聊BeanUtils之前,我們可以先了解一下PO VO BO DTO 。

  1. PO (persistant object持久對(duì)象)與數(shù)據(jù)庫中對(duì)應(yīng)的對(duì)象。
  2. BO (business object業(yè)務(wù)對(duì)象)處理業(yè)務(wù)邏輯肯定會(huì)涉及到多張表的數(shù)據(jù),BO可以將多個(gè)PO對(duì)象整合一起進(jìn)行業(yè)務(wù)處理。
  3. VO (view object表現(xiàn)層對(duì)象) 傳遞至前端的對(duì)象,可以將私密的數(shù)據(jù)隱藏掉。
  4. DTO (Data Transfer Object數(shù)據(jù)傳輸對(duì)象)可以簡(jiǎn)單理解為前端POST傳遞數(shù)據(jù)至后端,后端可以用@RequestBody ObjectDTO 對(duì)象進(jìn)行接收處理。

那么在業(yè)務(wù)比較復(fù)雜的情況下,必然會(huì)出現(xiàn)各種DTO,BO,VO,PO相互轉(zhuǎn)換的代碼。在業(yè)務(wù)代碼中出現(xiàn)大量的GET,SET方法不美觀,容易出錯(cuò)而且耗費(fèi)精力。由此出現(xiàn)了很多的開源工具,例如springframework的BeanUtils,apache的BeanUtils,dozer等。

本文討論的是springframework包下的BeanUtils。

import org.springframework.beans.BeanUtils;
BeanUtils.copyProperties(Object source, Object target);

? 就是把source對(duì)象的屬性賦值給target對(duì)象,但是要注意的是BeanUtils并不會(huì)對(duì)null進(jìn)行處理,而是會(huì)將其作為屬性值直接賦值給target。

? 例如:

ObjectDTO
@Data
@AllArgsConstructor
public class ObjectDTO {
    String name;
    int age;
    OtherProperty otherProperty;
}
ObjectBO
@Data
@AllArgsConstructor
public class ObjectBO {
    String name;
    int age;
    OtherProperty otherProperty;
    double value;
}
OtherProperty
@Data
@AllArgsConstructor
public class OtherProperty {
    String propertyName;
}
main
public class CopyMain {
    public static void main(String[] args) {
        ObjectDTO objectDTO = new ObjectDTO("DTO對(duì)象", 18, new OtherProperty("DTO其他屬性"));
        ObjectBO objectBO = new ObjectBO("BO對(duì)象",20,new OtherProperty("BO其他屬性"),1.0);
        BeanUtils.copyProperties(objectDTO,objectBO);
        //ObjectDTO(name=DTO對(duì)象, age=18, otherProperty=OtherProperty(propertyName=DTO其他屬性))
        System.out.println(objectDTO);
        //ObjectBO(name=DTO對(duì)象, age=18, otherProperty=OtherProperty(propertyName=DTO其他屬性), value=1.0)
        System.out.println(objectBO);
        //true
        System.out.println(objectDTO.getOtherProperty() == objectBO.getOtherProperty());
    }
}

由此可見source會(huì)將target所有符合條件的屬性進(jìn)行copy,如果屬性是引用對(duì)象,則會(huì)共享,屬于淺克隆。

如果將ObjectDTO的name屬性變更為null,otherProperty 設(shè)置為null,是否會(huì)跨過這些屬性進(jìn)行復(fù)制呢?

請(qǐng)看:

public static void main(String[] args) {
    ObjectDTO objectDTO = new ObjectDTO(null, 18, null);
        ObjectBO objectBO = new ObjectBO("BO對(duì)象",20,new OtherProperty("BO其他屬性"),1.0);
        BeanUtils.copyProperties(objectDTO,objectBO);
        //ObjectDTO(name=null, age=18, otherProperty=null)
        System.out.println(objectDTO);
        //ObjectBO(name=null, age=18, otherProperty=null, value=1.0)
        System.out.println(objectBO);
        //true
        System.out.println(objectDTO.getOtherProperty() == objectBO.getOtherProperty());
}

并不會(huì)跨過null進(jìn)行賦值,而是會(huì)進(jìn)行覆蓋。所以童鞋們要注意這一點(diǎn)哦,不然在修改操作的方法可能導(dǎo)致重要數(shù)據(jù)的丟失。:)

聊了這么多,我們看看BeanUtils的源碼是如何實(shí)現(xiàn)的吧!

/**
     * 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
     * @param ignoreProperties array of property names to ignore
     * @throws BeansException if the copying failed
     * @see BeanWrapper
     */
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable, @Nullable String... ignoreProperties) throws BeansException {
        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");
        Class<?> actualEditable = target.getClass();
        if (editable != null) {
            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;
        PropertyDescriptor[] var7 = targetPds;
        int var8 = targetPds.length;

        for(int var9 = 0; var9 < var8; ++var9) {
            PropertyDescriptor targetPd = var7[var9];
            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 var15) {
                            throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15);
                        }
                    }
                }
            }
        }

    }

細(xì)心的你,應(yīng)該發(fā)現(xiàn)了這樣的參數(shù) @Nullable String... ignoreProperties,顧名思義BeanUtils支持跨過某些屬性賦值。

public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException {
        copyProperties(source, target, null, ignoreProperties);
    }

想完成上面的跨過name,otherProperty,只需要

BeanUtils.copyProperties(objectDTO,objectBO,"name","otherProperty");

思考一下,如果一直通過反射來取值賦值,那么這個(gè)時(shí)間成本其實(shí)是比較大的,如果返回大量的數(shù)據(jù)轉(zhuǎn)成VO對(duì)象,那響應(yīng)的速度是非常慢的。先測(cè)試一下BeanUtils的效率。

    ObjectDTO objectDTO = new ObjectDTO("DTO", 18, new OtherProperty("property"));
        ObjectBO objectBO = new ObjectBO();
        ObjectBO objectBO1 = new ObjectBO();
        ObjectBO objectBO2 = new ObjectBO();



        long start = System.currentTimeMillis();
        BeanUtils.copyProperties(objectDTO,objectBO);
        long end1 = System.currentTimeMillis();
        BeanUtils.copyProperties(objectDTO,objectBO1);
        long end2 = System.currentTimeMillis();
        BeanUtils.copyProperties(objectDTO,objectBO2);
        long end3 = System.currentTimeMillis();

        System.out.println("第一次復(fù)制花費(fèi)的時(shí)間:" + (end1 - start));
        System.out.println("第二次復(fù)制花費(fèi)的時(shí)間:" + (end2 - end1));
        System.out.println("第三次復(fù)制花費(fèi)的時(shí)間:" + (end3 - end2));

結(jié)果如下:

第一次復(fù)制花費(fèi)的時(shí)間:679
第二次復(fù)制花費(fèi)的時(shí)間:0
第三次復(fù)制花費(fèi)的時(shí)間:0

為什么出現(xiàn)這樣的情況呢?在正常的預(yù)期中應(yīng)該是 679*n 才對(duì)吧,讓我們看看BeanUtils是怎么優(yōu)化的。

請(qǐng)注意上面源碼的這一行

PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);

只是一個(gè)簡(jiǎn)單的獲得所有屬性的PropertyDescriptor集合的方法。

/**
     * Retrieve the JavaBeans {@code PropertyDescriptor}s of a given class.
     * @param clazz the Class to retrieve the PropertyDescriptors for
     * @return an array of {@code PropertyDescriptors} for the given class
     * @throws BeansException if PropertyDescriptor look fails
     */
    public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException {
        CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
        return cr.getPropertyDescriptors();
    }

繼續(xù)深挖。

/**
     * Create CachedIntrospectionResults for the given bean class.
     * @param beanClass the bean class to analyze
     * @return the corresponding CachedIntrospectionResults
     * @throws BeansException in case of introspection failure
     */
    @SuppressWarnings("unchecked")
    static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
        CachedIntrospectionResults results = strongClassCache.get(beanClass);
        if (results != null) {
            return results;
        }
        results = softClassCache.get(beanClass);
        if (results != null) {
            return results;
        }

        results = new CachedIntrospectionResults(beanClass);
        ConcurrentMap<Class<?>, CachedIntrospectionResults> classCacheToUse;

        if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) ||
                isClassLoaderAccepted(beanClass.getClassLoader())) {
            classCacheToUse = strongClassCache;
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
            }
            classCacheToUse = softClassCache;
        }

        CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results);
        return (existing != null ? existing : results);
    }

原來BeanUtils會(huì)從強(qiáng)緩存中查找是否有當(dāng)前類的屬性集合,沒有就去弱緩存中查找。如果都沒有,才會(huì)進(jìn)行創(chuàng)建創(chuàng)建。strongClassCache,softClassCache本質(zhì)上其實(shí)是Map,class作為key,將創(chuàng)建的CachedIntrospectionResults作為value進(jìn)行存儲(chǔ)。源類也一樣將PropertyDescriptor緩存到CachedIntrospectionResults中。因此大大提升了性能.

  • 注意: 如果有父類的話,父類的屬性也會(huì)緩存起來。

參考CNblogs鏈接
[CSDN](https://blog.csdn.net/lz710117239/article/details/84781834

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容