Spring 之 BeanUtils.copyProperties(...) 源码简读

Posted 三流

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 之 BeanUtils.copyProperties(...) 源码简读相关的知识,希望对你有一定的参考价值。

零 版本

  • spring-beans 5.3.7

一 copyProperties

copyProperties(...) 在 org.springframework.beans.BeanUtils 下,在正常应用中,开发者如果需要转换 bean,通常使用到的方法是:

// originBean 是原型
// targetBean 是要转换的目标
BeanUtils.copyProperties(originBean, targetBean);

在 BeanUtils 中,copyProperties 有一系列的重载方法,但是最后都会落到一个具体的实现上:

/**
 * source - origin bean
 * target - 目标 bean
 * editable - 这个 class 对象用于设置对 target 的抽象层次
 * ignoreProperties - 要忽略的参数
 **/
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
                                   @Nullable String... ignoreProperties) throws BeansException {

    // 判空,两个主要的 java 对象是不可为空的
    Assert.notNull(source, "Source must not be null");
    Assert.notNull(target, "Target must not be null");

    /**
     * 获取 target 的 class 对象,如果设置了泛型,则默认使用泛型
     * 如果 editable 是 null,则此处忽略
     * 通常情况下是 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;
    }
    
    // 此处获取 target class 中的所有属性的描述
    PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
    
    // 是否有需要忽略的属性
    List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);

    // 此处轮询所有的属性
    for (PropertyDescriptor targetPd : targetPds) {
        // 获取写入方法,一般来说即为 setXX(...)
        Method writeMethod = targetPd.getWriteMethod();
        
        // 确认此属性并不被忽略,且存在写入方法
        if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
            
            PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
            if (sourcePd != null) {
                // 此处获取 source 中的对应属性的读取方法,一般来说即为 getXX(...)
                Method readMethod = sourcePd.getReadMethod();
                if (readMethod != null) {
                    // 获取 readMethod 的返回值类型
                    ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);
                    // 获取 writeMethod 的第一个入参类型
                    ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);

                    // 此处判断 readMethod 的返回类型和 writeMethod 的入参类型是否为继承关系
                    // 只有当这两者为继承关系,或者相等的情况下,才会进行注入
                    boolean isAssignable =
                        (sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ?
                         ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
                         targetResolvableType.isAssignableFrom(sourceResolvableType));

                    if (isAssignable) {
                        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);
                        }
                    }
                }
            }
        }
    }
}

二 总结

  • 要使用该方法,必须有 get/set 方法
  • 该方法是一个浅拷贝,读取和写入的对象必须相同或者有父子继承关系(也可以是接口)
  • 该方法不会抛错,只会忽略有问题的属性

以上是关于Spring 之 BeanUtils.copyProperties(...) 源码简读的主要内容,如果未能解决你的问题,请参考以下文章

风暴之Spring事务

我们一起学习Spring之Spring简介

Spring系列之手写一个SpringMVC

一文彻底解密Spring 源码之Spring MVC

java之spring之helloword

Spring之基础