Should we use clone or BeanUtils.copyProperties and why

前端 未结 7 1732
醉话见心
醉话见心 2020-12-13 04:47

By the looks of it - BeanUtils.copyProperties seems to create a clone of an object. If this is the case, and what with the concerns around implementing the Clon

7条回答
  •  天涯浪人
    2020-12-13 05:25

    I have checked the source code and I found that it is only copying the "first level" of primitive properties. When it comes to a nested object, the nested properties are still referencing the original object's fields, so it is not a "deep copy".

    Check this snippets from Spring source code from org.springframework.beans.BeanUtils.java, version 5.1.3:

    /**
         * Copy the property values of the given source bean into the 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. *

    This is just a convenience method. For more complex transfer needs, * consider using a full BeanWrapper. * @param source the source bean * @param target the target bean * @throws BeansException if the copying failed * @see BeanWrapper */ public static void copyProperties(Object source, Object target) throws BeansException { copyProperties(source, target, null, (String[]) null); } ... /** * 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 * @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 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); } } } } } }

    Just focus on this line:

    writeMethod.invoke(target, value);
    

    This line calls the setter on the target object. Imagine this class:

    class Student {
        private String name;
        private Address address;
    }
    

    If we have student1 and student2, the second is just intanciated and not assigned any fields, student1 has address1 and name John.

    So, if we call:

        BeanUtils.copyProperties(student1, student2);
    

    We are doing:

        student2.setName(student1.getName()); // this is copy because String is immutable
        student2.setAddress(student1.getAddress()); // this is NOT copy, we still are referencing `address1`
    

    When address1 changes, it changes student2 too.

    So, BeanUtils.copyProperties() only works on primitive types fields of the object; if it is nested, it does not work; or, you have to make sure the immutability of the original object during the whole lifecycle of the target object, which, is not easy and desirable.


    If you really want to make it a deep copy, you have to implement some way to recursively calls this method on fields which are not primitives. At last you will reach a class with only primitive/immutable fields and then you are done.

提交回复
热议问题