解決BeanUtils和PropertyUtils的From對象裏的null值會覆蓋To對象裏非空值的問題,提高複製效率和性能 如何高效率實現一個複製對象屬性方法工具?

解決BeanUtils和PropertyUtils的From對象裏的null值會覆蓋To對象裏非空值的問題,提高複製效率和性能 如何高效率實現一個複製對象屬性方法工具?

 

背景:

在商業項目中所需要的業務非常多,所以我們的業務數據也會有很多種,這個時候就會有什麼VO,DTO,PO等等這些,把業務和我們的基礎數據進行分離轉換。但是一直都沒有什麼好一點的轉換類。後來用了一下BeanUtils.copyPropertie,和PropertyUtils.copyProperties()的方法,發現其效率非常低。這裏也簡單總結了一下他們的用法及原理以及自己實現的轉換類;

用法:

BeanUtils.copyProperties("轉換後的類", "要轉換的類");

PropertyUtils.copyProperties("轉換後的類", "要轉換的類");

用法其實很簡單,第一個參數是轉換後的類,第二個參數是待轉換的類;我們可以理解成爲後轉前;

原理:

其原理是通過JDK自帶的反射機制動態的去get,set,從而去轉換我們的類。但是要注意一點他們所支持的數據類型,還有一個就是假如一個類裏面又寫了一個類,例如這種:

public class Name{

}

class Name1{

}

一般叫做內部類,像這種類進行轉換的時候,是不會成功的。因爲在裏面進行讀寫校驗的時候不會通過;

 

PropertyDescriptor[] origDescriptors =

               getPropertyDescriptors(orig);

           for (int i = 0; i < origDescriptors.length; i++) {

               String name = origDescriptors[i].getName();

               if (isReadable(orig, name) && isWriteable(dest, name)) {

                   try {

                       Object value = getSimpleProperty(orig, name);

                       if (dest instanceof DynaBean) {

                           ((DynaBean) dest).set(name, value);

                       } else {

                               setSimpleProperty(dest, name, value);

                       }

                   } catch (NoSuchMethodException e) {

                       if (log.isDebugEnabled()) {

                           log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);

                       }

                   }

               }

           }

 

上面是JDK的源代碼,我們執行isRead和isWrite的時候並不會通過;

注意差異,

主要的區別在於BeanUtils提供類型轉換功能,即發現兩個JavaBean的同名屬性爲不同類型時,在支持的數據類型範圍內進行轉換,而前者不支持這個功能,但是速度會更快一些。BeanUtils支持的轉換類型如下:

* java.lang.BigDecimal

* java.lang.BigInteger

* boolean and java.lang.Boolean

* byte and java.lang.Byte

* char and java.lang.Character

* java.lang.Class

* double and java.lang.Double

* float and java.lang.Float

* int and java.lang.Integer

* long and java.lang.Long

* short and java.lang.Short

* java.lang.String

* java.sql.Date

* java.sql.Time

* java.sql.Timestamp

閱讀其源碼,發現其內部是使用了裝飾者模式,我發現Java得工具類很喜歡使用這種模式,而且也十分好用;

他們都使用到了BeanutilsBean和PropertyUtilsBean只不過BeanUtils多了一個轉換的功能而已,但是性能上要比

PropertyUtils慢一些,其實兩個都很慢,最好不要使用;

重寫反射轉換:

 

/**

 * @param obj 轉換的對象值

 * @param clz 類對象

 * @return 轉換後的對象

 */

public static<T> T transferObject(Object obj,Class clz){

 T result = null;

 if(obj!=null&&!obj.equals("")){

 Method[] methods =  obj.getClass().getMethods();

 try {

  result = (T)clz.newInstance();

 } catch (Exception e1) {

  return null;

 }

 Method m;

 for(int i=0;i<methods.length;i++){

  m = methods[i];

  try {

   if(m.getName().startsWith("set")){

    String fieldName =  m.getName().replaceFirst("set", "");

    Method method = result.getClass().getMethod(m.getName(), m.getParameterTypes());

    Method getMethod = obj.getClass().getMethod("get"+fieldName, new Class[]{});

    method.invoke(result, getMethod.invoke(obj, new Object[]{}));

   }

  } catch (Exception e) {

   continue;

  }

 }

 }

 return result;

}

 

上面這個方法也是用了Java反射去寫的,但是少了很多校驗以及轉換。所以在100萬條數據的時候,效率是3739毫秒,而使用BeanUtils是5000毫秒左右。兩個效率都不高;

ReflectASM,高性能的反射:

什麼是ReflectASM    ReflectASM是一個很小的java類庫,主要是通過asm生產類來實現java反射,執行速度非常快,看了網上很多和反射的對比,覺得ReflectASM比較神奇,很想知道其原理,下面介紹下如何使用及原理;

 

public static void main(String[] args) {  

       User user = new User();  

       //使用reflectasm生產User訪問類  

       MethodAccess access = MethodAccess.get(User.class);  

       //invoke setName方法name值  

       access.invoke(user, "setName", "張三");  

       //invoke getName方法 獲得值  

       String name = (String)access.invoke(user, "getName", null);  

       System.out.println(name);  

   }  

原理 

  上面代碼的確實現反射的功能,代碼主要的核心是 MethodAccess.get(User.class); 

看了下源碼,這段代碼主要是通過asm生產一個User的處理類 UserMethodAccess(這個類主要是實現了invoke方法)的ByteCode,然後獲得該對象,通過上面的invoke操作user類。 

ASM反射轉換:

 

private static Map<Class, MethodAccess> methodMap = new HashMap<Class, MethodAccess>();

private static Map<String, Integer> methodIndexMap = new HashMap<String, Integer>();

private static Map<Class, List<String>> fieldMap = new HashMap<Class, List<String>>();

public static void copyProperties(Object desc, Object orgi) {

 MethodAccess descMethodAccess = methodMap.get(desc.getClass());

 if (descMethodAccess == null) {

  descMethodAccess = cache(desc);

 }

 MethodAccess orgiMethodAccess = methodMap.get(orgi.getClass());

 if (orgiMethodAccess == null) {

  orgiMethodAccess = cache(orgi);

 }

 List<String> fieldList = fieldMap.get(orgi.getClass());

 for (String field : fieldList) {

  String getKey = orgi.getClass().getName() + "." + "get" + field;

  String setkey = desc.getClass().getName() + "." + "set" + field;

  Integer setIndex = methodIndexMap.get(setkey);

  if (setIndex != null) {

   int getIndex = methodIndexMap.get(getKey);

   // 參數一需要反射的對象

   // 參數二class.getDeclaredMethods 對應方法的index

   // 參數對三象集合

   descMethodAccess.invoke(desc, setIndex.intValue(),

     orgiMethodAccess.invoke(orgi, getIndex));

  }

 }

}

// 單例模式

private static MethodAccess cache(Object orgi) {

 synchronized (orgi.getClass()) {

  MethodAccess methodAccess = MethodAccess.get(orgi.getClass());

  Field[] fields = orgi.getClass().getDeclaredFields();

  List<String> fieldList = new ArrayList<String>(fields.length);

  for (Field field : fields) {

   if (Modifier.isPrivate(field.getModifiers())

     && !Modifier.isStatic(field.getModifiers())) { // 是否是私有的,是否是靜態的

    // 非公共私有變量

    String fieldName = StringUtils.capitalize(field.getName()); // 獲取屬性名稱

    int getIndex = methodAccess.getIndex("get" + fieldName); // 獲取get方法的下標

    int setIndex = methodAccess.getIndex("set" + fieldName); // 獲取set方法的下標

    methodIndexMap.put(orgi.getClass().getName() + "." + "get"

      + fieldName, getIndex); // 將類名get方法名,方法下標註冊到map中

    methodIndexMap.put(orgi.getClass().getName() + "." + "set"

      + fieldName, setIndex); // 將類名set方法名,方法下標註冊到map中

    fieldList.add(fieldName); // 將屬性名稱放入集合裏

   }

  }

  fieldMap.put(orgi.getClass(), fieldList); // 將類名,屬性名稱註冊到map中

  methodMap.put(orgi.getClass(), methodAccess);

  return methodAccess;

 }

}

 

執行1000000條效率80幾毫秒,效率已經沒問題了;

---------------------  

通過以上分析總結,假如我們用BeanUtil或PropertyUtils裏的copyProperties()的方法或者我們自己用ReflectASM的高性能的反射編寫自己的copyProperties()的方法,我發現把具有相同屬性名的B對象複製到A對象,假如A對象的name屬性有值,而B對象裏的屬性name爲null,則B會把A裏的name值覆蓋掉,使A的name爲null,這個不是我們所期望的,於是就有了下面的解決方案:

解決BeanUtils和PropertyUtils的From對象裏的null值會覆蓋To對象裏非空值的問題,提高複製效率和性能

添加ReflectASM類庫代碼:

 

<dependency>
      <groupId>com.esotericsoftware</groupId>
      <artifactId>reflectasm</artifactId>
      <version>1.11.7</version>
    </dependency>

重新實現CopyUtils的copyProperties方法:

繼續閱讀

 

來源:“創享視界”,創享視界(creativeview.cn)是一個帶動全民顛覆八小時工作制,通過投稿把自己的創意智慧變現的方式創造被動收入,從而實現財務自由的平臺。我們相信,創新思維不僅有助於打造更出色的產品,還可以讓世界變得更美好,讓人人受益。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章