关于java:Spring-之-BeanUtilscopyProperties-源码简读

零 版本

  • 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 办法
  • 该办法是一个浅拷贝,读取和写入的对象必须雷同或者有父子继承关系(也能够是接口)
  • 该办法不会抛错,只会疏忽有问题的属性

【腾讯云】云产品限时秒杀,爆款1核2G云服务器,首年99元

阿里云限时活动-2核2G-5M带宽-40-100G SSD服务器,特惠价86元/年(原价724元/年,限时99元续购三次),速抢

本文由乐趣区整理发布,转载请注明出处,谢谢。

You may also like...

发表评论

邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据