1. 程式人生 > 程式設計 >淺析Java中Apache BeanUtils和Spring BeanUtils的用法

淺析Java中Apache BeanUtils和Spring BeanUtils的用法

# 前言

在我們實際專案開發過程中,我們經常需要將不同的兩個物件例項進行屬性複製,從而基於源物件的屬性資訊進行後續操作,而不改變源物件的屬性資訊,比如DTO資料傳輸物件和資料物件DO,我們需要將DO物件進行屬性複製到DTO,但是物件格式又不一樣,所以我們需要編寫對映程式碼將物件中的屬性值從一種型別轉換成另一種型別。

# 物件拷貝

在具體介紹兩種 BeanUtils 之前,先來補充一些基礎知識。它們兩種工具本質上就是物件拷貝工具,而物件拷貝又分為深拷貝和淺拷貝,下面進行詳細解釋。

# 什麼是淺拷貝和深拷貝

在Java中,除了 基本資料型別之外,還存在 類的例項物件這個引用資料型別,而一般使用 “=”號做賦值操作的時候,對於基本資料型別,實際上是拷貝的它的值,但是對於物件而言,其實賦值的只是這個物件的引用,將原物件的引用傳遞過去,他們實際還是指向的同一個物件。

而淺拷貝和深拷貝就是在這個基礎上做的區分,如果在拷貝這個物件的時候,只對基本資料型別進行了拷貝,而對引用資料型別只是進行引用的傳遞,而沒有真實的建立一個新的物件,則認為是淺拷貝。反之,在對引用資料型別進行拷貝的時候,建立了一個新的物件,並且複製其內的成員變數,則認為是深拷貝。

簡單來說:

淺拷貝:對基本資料型別進行值傳遞,對引用資料型別進行引用傳遞般的拷貝,此為淺拷貝

深拷貝:對基本資料型別進行值傳遞,對引用資料型別,建立一個新的物件,並複製其內容,此為深拷貝。

兩難!到底用Apache BeanUtils還是Spring BeanUtils?

# BeanUtils

前面簡單講了一下物件拷貝的一些知識,下面就來具體看下兩種 BeanUtils 工具

# Apache 的 BeanUtils

首先來看一個非常簡單的BeanUtils的例子

publicclass PersonSource {
 private Integer id;
 private String username;
 private String password;
 private Integer age;
 // getters/setters omiited
}
publicclass PersonDest {
 private Integer id;
 private String username;
 private Integer age;
 // getters/setters omiited
}
publicclass TestApacheBeanUtils {
 public static void main(String[] args) throws InvocationTargetException,IllegalAccessException {
  //下面只是用於單獨測試
  PersonSource personSource = new PersonSource(1,"pjmike","12345",21);
  PersonDest personDest = new PersonDest();
  BeanUtils.copyProperties(personDest,personSource);
  System.out.println("persondest: "+personDest);
 }
}
persondest: PersonDest{id=1,username='pjmike',age=21}

從上面的例子可以看出,物件拷貝非常簡單,BeanUtils最常用的方法就是:

//將源物件中的值拷貝到目標物件//將源物件中的值拷貝到目標物件
public static void copyProperties(Object dest,Object orig) throws IllegalAccessException,InvocationTargetException {
 BeanUtilsBean.getInstance().copyProperties(dest,orig);
}

但是由於 Apache下的BeanUtils物件拷貝效能太差,不建議使用,而且在阿里巴巴Java開發規約外掛上也明確指出:

Ali-Check | 避免用Apache Beanutils進行屬性的copy。

commons-beantutils 對於物件拷貝加了很多的檢驗,包括型別的轉換,甚至還會檢驗物件所屬的類的可訪問性,可謂相當複雜,這也造就了它的差勁的效能,具體實現程式碼如下:

public void copyProperties(final Object dest,final Object orig)
  throws IllegalAccessException,InvocationTargetException {

  // Validate existence of the specified beans
  if (dest == null) {
   thrownew IllegalArgumentException
     ("No destination bean specified");
  }
  if (orig == null) {
   thrownew IllegalArgumentException("No origin bean specified");
  }
  if (log.isDebugEnabled()) {
   log.debug("BeanUtils.copyProperties(" + dest + "," +
      orig + ")");
  }

  // Copy the properties,converting as necessary
  if (orig instanceof DynaBean) {
   final DynaProperty[] origDescriptors =
    ((DynaBean) orig).getDynaClass().getDynaProperties();
   for (DynaProperty origDescriptor : origDescriptors) {
    final String name = origDescriptor.getName();
    // Need to check isReadable() for WrapDynaBean
    // (see Jira issue# BEANUTILS-61)
    if (getPropertyUtils().isReadable(orig,name) &&
     getPropertyUtils().isWriteable(dest,name)) {
     final Object value = ((DynaBean) orig).get(name);
     copyProperty(dest,name,value);
    }
   }
  } elseif (orig instanceof Map) {
   @SuppressWarnings("unchecked")
   final
   // Map properties are always of type <String,Object>
   Map<String,Object> propMap = (Map<String,Object>) orig;
   for (final Map.Entry<String,Object> entry : propMap.entrySet()) {
    final String name = entry.getKey();
    if (getPropertyUtils().isWriteable(dest,name)) {
     copyProperty(dest,entry.getValue());
    }
   }
  } else/* if (orig is a standard JavaBean) */ {
   final PropertyDescriptor[] origDescriptors =
    getPropertyUtils().getPropertyDescriptors(orig);
   for (PropertyDescriptor origDescriptor : origDescriptors) {
    final String name = origDescriptor.getName();
    if ("class".equals(name)) {
     continue; // No point in trying to set an object's class
    }
    if (getPropertyUtils().isReadable(orig,name)) {
     try {
      final Object value =
       getPropertyUtils().getSimpleProperty(orig,name);
      copyProperty(dest,value);
     } catch (final NoSuchMethodException e) {
      // Should not happen
     }
    }
   }
  }

 }

# Spring 的 BeanUtils

使用spring的BeanUtils進行物件拷貝:

publicclass TestSpringBeanUtils {
 public static void main(String[] args) throws InvocationTargetException,IllegalAccessException {

  //下面只是用於單獨測試
  PersonSource personSource = new PersonSource(1,21);
  PersonDest personDest = new PersonDest();
  BeanUtils.copyProperties(personSource,personDest);
  System.out.println("persondest: "+personDest);
 }
}

Spring下的BeanUtils也是使用 copyProperties方法進行拷貝,只不過它的實現方式非常簡單,就是對兩個物件中相同名字的屬性進行簡單的get/set,僅檢查屬性的可訪問性。具體實現如下:

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);
   }
   }
  }
  }
 }
 }

可以看到,成員變數賦值是基於目標物件的成員列表,並且會跳過ignore的以及在源物件中不存在,所以這個方法是安全的,不會因為兩個物件之間的結構差異導致錯誤,但是必須保證同名的兩個成員變數型別相同

# 小結

以上簡要的分析兩種BeanUtils,因為Apache下的BeanUtils效能較差,不建議使用,可以使用 Spring的BeanUtils ,或者使用其他拷貝框架,比如:Dozer、ModelMapper等等

到此這篇關於淺析Java中Apache BeanUtils和Spring BeanUtils的用法的文章就介紹到這了,更多相關Apache BeanUtils和Spring BeanUtils內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!