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


image.png


image.png
獲取的是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)


image.png

摘要總結: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

參考資料

https://www.cnblogs.com/kaka/archive/2013/03/06/2945514.html


Java編程技術樂園:分享干貨技術,每天進步一點點,小的積累,帶來大的改變!

掃描關注,后台回復【秘籍】,獲取珍藏干貨! 99.9%的伙伴都很喜歡

關注不迷路| center| 747x519


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM