BeanUtils#copyProperties 属性拷贝

1,487 阅读3分钟

深拷贝和浅拷贝

简单说拷贝就是将一个类中的属性拷贝到另一个中,对于BeanUtils.copyProperties来说,你必须保证属性名是相同的,因为它是根据get和set方法来赋值的。

浅拷贝可以理解为如果是引用类型,那么目标对象拷贝的只是源对象的地址,无论目标对象还是源对象改变,他们都会一起改变。

深拷贝就是将目标对象的属性全部复制一份给源对象,复制完之后他们就是隔开的,没有任何关系,无论操作源对象还是目标对象都对另一个没有影响。

无论是浅拷贝还是深拷贝,对于基本类型和String来说都是没有影响的,有影响的只有引用类型数据

copyProperties 方法使用

个人理解,通过下面的代码也许你会跟我一样开始认为,网上深浅拷贝的定义是没有问题的,但是对于很多方法,说方法是深浅拷贝是有问题的。具体让我们来看代码吧!

public class BeanUtilsTest {

    public static void main(String[] args) {
        UserDto userDto = new UserDto();
        userDto.setName("张三");
        userDto.setName("138888888888");
        AddressDto address = new AddressDto();
        address.setPhone("13888888111");
        address.setName("张三三");
        address.setAddressDetail("北京-海淀");
        userDto.setAddress(address);

        UserDto userDto1 = new UserDto();
        BeanUtils.copyProperties(userDto, userDto1);

        System.out.println("userDto1.address.addressDetail" + userDto1.getAddress().getAddressDetail());
        address.setAddressDetail("北京-朝阳");
        System.out.println();
        System.out.println("userDto.address.addressDetail" + userDto.getAddress().getAddressDetail());
        System.out.println("userDto1.address.addressDetail" + userDto1.getAddress().getAddressDetail());
    }
}

@Data
class UserDto {
    private String name;
    private String phone;
    private AddressDto address;
}

@Data
class AddressDto {
    private String name;
    private String phone;
    private String addressDetail;
}

输出结果如下:

userDto1.address.addressDetail北京-海淀

userDto.address.addressDetail北京-朝阳
userDto1.address.addressDetail北京-朝阳

源码分析

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
    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;
    }
    // 1. 获取目标 Class 的属性描述
    PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
    List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);

    // 2. 循环遍历 class 的属性
    for (PropertyDescriptor targetPd : targetPds) {
        // Class 属性的 set 方法, setXXX
        Method writeMethod = targetPd.getWriteMethod();
        // 3.如果存在写方法,并且该属性不忽略,继续往下走,否则跳过继续遍历
        if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
            // 4.获取源Class的与目标属性同名的属性描述
            PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
            // 5.如果源属性描述不存在直接跳过,否则继续往下走
            if (sourcePd != null) {
                // 获取源属性描述的读方法
                Method readMethod = sourcePd.getReadMethod();
                // 6.如果源属性描述的读防范存在且返回数据类型和目标属性的写方法入参类型相同或者派生
                // 继续往下走,否则直接跳过继续下次遍历
                if (readMethod != null) {
                    ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);
                    ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);

                    // Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
                    // 如果任一ResolvableType具有不可解析的泛型,则忽略assignable check中的泛型类型。
                    boolean isAssignable =
                        (sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ?
                         ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
                         targetResolvableType.isAssignableFrom(sourceResolvableType));
                    
                    if (isAssignable) {
                        try {
                            // 如果源属性读方法修饰符不是public,那么修改为可访问
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }
                            Object value = readMethod.invoke(source);
                            // 如果目标属性的写方法修饰符不是public,则修改为可访问
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            // 8.通过反射将源属性值赋值给目标属性
                            writeMethod.invoke(target, value);
                        }
                        catch (Throwable ex) {
                            throw new FatalBeanException(
                                "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                        }
                    }
                }
            }
        }
    }
}

由于该方法是通过反射的方式去重新构造一个对象,因此不管原先的目标对象中已经存在什么参数值,都会被新的想要复制的对象的参数进行覆盖。

我们也可以看到,在属性的拷贝过程中,并没有特殊处理,引用类型的属性,那么 BeanUtils.copyProperties 本质也是实现浅拷贝。