BeanUtils.copyProperties 的使用

Spring 的 BeanUtils.copyProperties(Object source, Object target);

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

场景

将父类的属性拷贝到子类中

  • 一个一个通过 set 设置值

  • 用 BeanUtils.copyProperties 【方便】

BeanUtils 是深拷贝还是浅拷贝

BeanUtils 是浅拷贝

  • 浅拷贝:只是调用子对象的 set 方法,并没有将所有属性拷贝。【引用的一个内存地址】

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

  • 深拷贝:将子对象的属性也拷贝过去。

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

使用方法

 
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 本质也是实现浅拷贝。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值