天天看點

beanutils工具類_Java常用屬性拷貝工具類使用總結

beanutils工具類_Java常用屬性拷貝工具類使用總結

開頭聊幾句

  • 1、網上很多的技術文章和資料是有問題的,要學會辨證的看待,不能随便就拿來用,起碼要自己驗證一下
  • 2、關注當下,關注此刻,如果你真正閱讀本篇文章,請花幾分鐘時間的注意力閱讀,相信你會有收獲的
  • 3、文中代碼沒有使用圖檔展示,可能存在閱讀排版錯亂問題,請見諒,因為可能考慮到有其他夥伴需要拷貝代碼,這樣比較友善

Java常用屬性拷貝工具類使用總結

對項目中經常使用的屬性拷貝工具類進行總結:

  • org.apache.commons.beanutils.BeanUtils
  • org.apache.commons.beanutils.PropertyUtils
  • org.springframework.beans.BeanUtils

本文使用的工具類對應的版本:

commons-beanutils:1.9.4

spring-beans:5.0.7.RELEASE

字段和屬性

首先明确下在Java中字段和屬性的差別。

屬性是不是類裡最上邊的那些全局變量嗎?比如:

public class UserTest{
    private String userName;
    private String password;


    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getHello() {
        return "hello";
    }

    public void setHello(String str) {
    }
}
           

上面 private String userName;private String password;。準确的來說它們應該稱為:字段,而不是本次要講的屬性。

下面簡述一下:

什麼是Java中的屬性

Java中的屬性(property),通常可以了解為get和set方法,而字段(field),通常叫做“類成員”,或“類成員變量”,有時也叫“域”,了解為“資料成員”,用來承載資料的。

直白點就是Java中的屬性是指:設定和讀取字段的方法,也就是平常見到的set和get方法。

隻要是set和get開頭的方法在Java裡都認為它是屬性

(請注意這句話,等下後邊會寫代碼做驗證)

屬性名稱:就是set和get方法名 去掉"set"和"get"後的内容

比如:

public void setUserName(String userName) {
 this.userName = userName;
}
           

它的屬性名稱是:userName(也就是方法名稱”setUserName”去掉“set”)

當然 setUserName和 getUserName 方法是指同一個屬性 UserName,

這裡再次提醒:字段和屬性不是同一個東西。 代碼驗證屬性

上面代碼中還有一個

getHello

setHello

, JDK 中有個API

Introspector
beanutils工具類_Java常用屬性拷貝工具類使用總結
beanutils工具類_Java常用屬性拷貝工具類使用總結

擷取的是java.beans.BeanInfo 類。這個類可以通過

java.beans.BeanInfo#getPropertyDescriptors : 擷取java bean 所有的屬性。
public static void main(String[] args) throws IntrospectionException {
    BeanInfo beanInfo = Introspector.getBeanInfo(UserTest.class);
    // 得到類中的所有的屬性描述器
    PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
    System.out.println("屬性的個數:" + pds.length);
    for (PropertyDescriptor pd : pds) {
        System.out.println("屬性:" + pd.getName());
    }
}
           

結果:

屬性的個數:4
屬性:class
屬性:hello
屬性:password
屬性:userName
           

上面多了一個 class ,原因很簡單,因為Object類是所有類的父類,Object類裡有個方法叫 getClass();

是以這也驗證了咱們剛才說的:

“隻要是set或者get開頭的方法都叫屬性”

使用說明

default (即預設,什麼也不寫): 在同一包内可見,不使用任何修飾符。使用對象:類、接口、變量、方法。 public : 對所有類可見。使用對象:類、接口、變量、方法 private : 在同一類内可見。使用對象:變量、方法。 注意:不能修飾類(外部類) protected : 對同一包内的類和所有子類可見。使用對象:變量、方法。 注意:不能修飾類(外部類)

org.springframework.beans.BeanUtils#copyProperties

1.基本類型和包裝類型會自動轉換, 方法名稱相同,傳回值類型和參數類型不同,不進行複制,也不報錯_

2.支援指定忽略某些屬性不複制

3、支援類的修飾符 default 、 public

org.apache.commons.beanutils.PropertyUtils#copyProperties

1.基本類型和包裝類型會自動轉換

2.方法名稱相同,傳回值類型和參數類型不同,複制失敗,會報錯,如下:

_argument type mismatch - had objects of type "java.lang.Double" but expected signature "java.lang.String"

3.隻支援類的修飾符 public,如果是default 則直接不會進行轉換(注意内部類複制也要加public)

org.apache.commons.beanutils.BeanUtils#_copyProperties

1.基本類型和包裝類型會自動轉換

2.方法名稱相同,傳回值類型和_ _參數類型不同,不複制,不報錯

3.隻支援類的修飾符 public,如果是default 則直接不會進行轉換(注意内部類複制也要加public)

tips:

Spring和apache的_copyProperties_屬性的方法源和目的參數的位置正好相反,是以導包和調用的時候都要注意一下。

// Apache
public static void 
  copyProperties(final Object dest, final Object orig)
    
// Spring
public static void
  copyProperties(Object source, Object target)
           

性能參考:

Bean複制的幾種架構性能比較(Apache BeanUtils、PropertyUtils,Spring BeanUtils,Cglib BeanCopier)
beanutils工具類_Java常用屬性拷貝工具類使用總結

摘要總結:Spring是在次數增多的情況下,性能較好,在資料較少的時候,性能比PropertyUtils的性能差一些。PropertyUtils的性能相對穩定,表現是呈現線性增長的趨勢。而Apache的BeanUtil的性能最差,無論是單次Copy還是大數量的多次Copy性能都不是很好。

使用的壓測工具備忘:Java使用JMH進行簡單的基準測試Benchmark :

http://irfen.me/java-jmh-simple-microbenchmark/

根據上面的具體的分析還是使用 :

org.springframework.beans.BeanUtils#copyProperties 原因

1.這個方法在複制的時候不會因為屬性的不同而報錯,影響代碼執行

2.性能方面也相對較好

其他Apache的兩個,

1、org.apache.commons.beanutils.PropertyUtils#copyProperties 複制會直接報錯

2、org.apache.commons.beanutils.BeanUtils#copyProperties 性能相對較差

原理探索

核心本質都是使用反射實作。具體的實作代碼稍有不同。

Spring#BeanUtils

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

1、擷取 目标對象 所有的屬性 targetPds

PropertyDescriptor_[] _targetPds = getPropertyDescriptors(actualEditable);

2、循環 targetPds ,并在源對象取出對應的屬性

PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName_())_;

3、r如果不是修飾不是public,**暴力反射 ,**然後使用對屬性進行設值

setAccessible_( true) ;// 暴力反射 writeMethod.invoke( target, value)_;

### apache.commons#BeanUtils

  • org.apache.commons.beanutils.BeanUtilsBean#copyProperties

簡單截取核心代碼:

// org.apache.commons.beanutils.BeanUtilsBean#copyProperties

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) &&
        getPropertyUtils().isWriteable(dest, name)) {
        try {
            final Object value =
                getPropertyUtils().getSimpleProperty(orig, name);
            copyProperty(dest, name, value);
        } catch (final NoSuchMethodException e) {
            // Should not happen
        }
    }
}
// org.apache.commons.beanutils.BeanUtilsBean#copyProperty
getPropertyUtils().setSimpleProperty(target, propName, value);

// org.apache.commons.beanutils.PropertyUtilsBean#setSimpleProperty
 invokeMethod(writeMethod, bean, values);

           

1、 擷取的是源對象的所有的屬性

final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);

2、如果屬性是class,不複制

if ("class".equals_( name)) { continue; // No point in trying to set an object's class}_

3、循環源對象的屬性,做一些檢驗

copyProperty(dest, name, value); 1、會檢驗目标對象是否有源對象的屬性,沒有跳過 2、擷取屬性的名稱類型

4、然後給目标對象設定,最終還是使用反射

method.invoke_( bean, values)_;

apache.commons#PropertyUtils

  • org.apache.commons.beanutils.PropertyUtilsBean#copyProperties

簡單截取核心代碼:

// org.apache.commons.beanutils.PropertyUtilsBean#copyProperties
final PropertyDescriptor[] origDescriptors =
    getPropertyDescriptors(orig);
for (PropertyDescriptor origDescriptor : origDescriptors) {
    final String name = origDescriptor.getName();
    if (isReadable(orig, name) && isWriteable(dest, name)) {
        try {
            final Object value = getSimpleProperty(orig, name);
            if (dest instanceof DynaBean) {
                ((DynaBean) dest).set(name, value);
            } else {
                setSimpleProperty(dest, name, value);
            }
        } catch (final NoSuchMethodException e) {
            if (log.isDebugEnabled()) {
                log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
            }
        }
    }
}
// org.apache.commons.beanutils.PropertyUtilsBean#invokeMethod
method.invoke(bean, values);

           

1、 擷取的是源對象的所有的屬性

final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);

2、循環源對象的屬性,然後給目标對象設定,最終還是使用反射

總結

結合使用說明以及相關的性能和原理分析,建議使用

org.springframework.beans.BeanUtils#copyPropertie

參考資料

  • Bean複制的幾種架構性能比較(Apache BeanUtils、PropertyUtils,Spring BeanUtils,Cglib BeanCopier)
https://www.cnblogs.com/kaka/archive/2013/03/06/2945514.html
  • Java反射——内省(Introspector)以及BeanUtils内省架構 https://blog.csdn.net/ju_362204801/article/details/90672396
Java程式設計技術樂園

:分享幹貨技術,每天進步一點點,小的積累,帶來大的改變!

掃描關注,背景回複

【秘籍】

,擷取珍藏幹貨!

99.9%

的夥伴都很喜歡

beanutils工具類_Java常用屬性拷貝工具類使用總結

繼續閱讀