關於BeanUtils.copyProperties(source, target)的使用

BeanUtils.copyProperties

首先,使用的是org.springframework.beans.BeanUtils;

source 來源, target 目標

顧名思義, BeanUtils.copyProperties(source, target); 第一個參數是需要拷貝的目標,第二個參數是拷貝後的目標

因為這個方法有很多種情況,容易分不清,所以今天測瞭一下不同情況下的結果如何。

1.target裡面有source裡沒有的屬性

並且此屬性有值時

2.target和source相同屬性的值不一樣時

下面是沒有拷貝之前的值

在這裡插入圖片描述

拷貝之後

在這裡插入圖片描述

可以看到,target裡面不同值並沒有清空,而是保留瞭下來。而相中屬性本身存在的值被覆蓋。

3.當target和source裡面的屬性名相同而類型不同時

在這裡插入圖片描述

拷貝之後

在這裡插入圖片描述

類型不同的屬性無法拷貝。

Spring自帶BeanUtils.copyProperties(Object source, Object target)之坑

在java服務化項目中,客戶端和服務端之間交互經常用到BeanCopy,其目的是為瞭方便類之間的賦值,簡單方便,但是經常會遇到復合對象賦值不上去的情況,究其原因是對BeanUtils.copyProperties(Object source, Object target)方法底層源碼的不瞭解導致的,下面我來一步一步解釋其原因。

先看一個例子:

@Data
public class BdmTeamMonthNewStoreTopResult implements Serializable {
    private static final long serialVersionUID = -3251482519506276368L;
    /**
     * 排名列表
     */
    private List<BdmTeamMonthNewStoreTopInfo> topInfoList;
 
    /**
     * 我的排名信息
     */
    private BdmTeamMonthNewStoreTopMyInfo myTopInfo;
 
    @Override
    public String toString() {
        return ReflectionToStringBuilder.reflectionToString(this,
                ToStringStyle.SHORT_PREFIX_STYLE);
    }
}
@Data
public class MonthNewStoreTopInfoResponse implements Serializable {
    private static final long serialVersionUID = 4483822161951780674L;
    /**
     * 排名信息列表
     */
    private List<MonthNewStoreTopInfo> topInfoList;
    /**
     * 我的排名信息
     */
    private MonthNewStoreTopMyInfo myTopInfo;
 
    @Override
    public String toString() {
        return ReflectionToStringBuilder.reflectionToString(this,
                ToStringStyle.SHORT_PREFIX_STYLE);
    }
}

當我們用BeanUtils.copyProperties(monthNewStoreTopResponse , bdmTeamMonthNewStoreTopResult )時會發現myTopInfo這個對象賦值為null,這是為什麼呢?讓我們來看一看源碼:

//這是點進源碼的第一段代碼
  public static void copyProperties(Object source, Object target) throws BeansException {
    copyProperties(source, target, null, (String[]) null);
  }
  
  //這個才是copy的主代碼
  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<?> 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;
    }
    PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
    List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
 
    for (PropertyDescriptor targetPd : targetPds) {
      Method writeMethod = targetPd.getWriteMethod();
      if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
        PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
        if (sourcePd != null) {
          Method readMethod = sourcePd.getReadMethod();
          if (readMethod != null &&
              ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
            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);
            }
          }
        }
      }
    }
  }

其中ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())這個校驗起到瞭關鍵的作用,我們再進入這段代碼的源碼看一眼,源碼如下:

public static boolean isAssignable(Class<?> lhsType, Class<?> rhsType) {
    Assert.notNull(lhsType, "Left-hand side type must not be null");
    Assert.notNull(rhsType, "Right-hand side type must not be null");
    if (lhsType.isAssignableFrom(rhsType)) {
      return true;
    }
    if (lhsType.isPrimitive()) {
      Class<?> resolvedPrimitive = primitiveWrapperTypeMap.get(rhsType);
      if (lhsType == resolvedPrimitive) {
        return true;
      }
    }
    else {
      Class<?> resolvedWrapper = primitiveTypeToWrapperMap.get(rhsType);
      if (resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper)) {
        return true;
      }
    }
    return false;
  }

其中lhsType.isAssignableFrom(rhsType)判定此 Class 對象所表示的類或接口與指定的 Class 參數所表示的類或接口是否相同,或是否是其超類或超接口。

如果是則返回 true;否則返回 false。

如果該 Class表示一個基本類型,且指定的 Class 參數正是該 Class 對象,則該方法返回 true;否則返回 false。

意思其實就是說lhsType是不是rhsType的子類,如果是,則返回true,否則返回false。

這也就是說我們上面的例子MonthNewStoreTopMyInfo 對象和我們將要賦值的對象BdmTeamMonthNewStoreTopMyInfo 是同一個對象或者是它的子類才可以copy賦值成功,否則直接跳過返回瞭,不進行writeMethod.invoke(target, value)賦值;

哪為什麼topInfoList卻能賦值成功呢?

因為在lhsType.isAssignableFrom(rhsType)校驗的時候是判斷的是List類型的子類而不是List<BdmTeamMonthNewStoreTopMyInfo>中的BdmTeamMonthNewStoreTopMyInfo的子類。

所以我們在用Spring自帶BeanUtils.copyProperties(Object source, Object target)進行對象copy時候需要特別註意,如果變量為非java自帶的對象類型,則需要註意復合對象中的變量對象和被拷貝變量對象是同類型才可以。

如果MonthNewStoreTopMyInfo 和BdmTeamMonthNewStoreTopMyInfo不是同一個類型,可以通過先獲得MonthNewStoreTopMyInfo 這個對象再和需要賦值的對象BdmTeamMonthNewStoreTopMyInfo進行變量級別的調用BeanUtils.copyProperties(MonthNewStoreTopMyInfo , BdmTeamMonthNewStoreTopMyInfo),最後再把復制後的結果set進結果集。

最好的解決辦法是創建一個公共的model對象,替換MonthNewStoreTopMyInfo和BdmTeamMonthNewStoreTopMyInfo,這樣也少創建瞭一個類,同時也減少瞭代碼量,維護一份model,當有新增需求變化時,隻需要修改公共的model對象即可,簡單方便。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: