Spring 之 BeanUtils.copyProperties(...) 源碼簡讀

零 版本

  • spring-beans 5.3.7

一 copyProperties

copyProperties(...) 在 org.springframework.beans.BeanUtils 下,在正常應用中,開發者若是須要轉換 bean,一般使用到的方法是:java

// originBean 是原型
// targetBean 是要轉換的目標
BeanUtils.copyProperties(originBean, targetBean);

在 BeanUtils 中,copyProperties 有一系列的重載方法,可是最後都會落到一個具體的實現上:spring

/**
 * 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 方法
  • 該方法是一個淺拷貝,讀取和寫入的對象必須相同或者有父子繼承關係(也能夠是接口)
  • 該方法不會拋錯,只會忽略有問題的屬性
相關文章
相關標籤/搜索