基於Beanutils.copyProperties()的用法及重寫提高效率

Beanutils.copyProperties()用法及重寫提高效率

特別說明本文介紹的是Spring(import org.springframework.beans.BeanUtils)中的BeanUtils.copyProperties(A,B)方法。是將A中的值賦給B。apache(org.apache.commons.beanutils.BeanUtils)中的BeanUtils.copyProperties(A,B)方法是將B中的值賦值給A。

一、簡介

BeanUtils提供對Java反射和自省API的包裝。其主要目的是利用反射機制對JavaBean的屬性進行處理。我們知道,一個JavaBean通常包含瞭大量的屬性,很多情況下,對JavaBean的處理導致大量get/set代碼堆積,增加瞭代碼長度和閱讀代碼的難度。

二、用法

如果你有兩個具有很多相同屬性的JavaBean,一個很常見的情況就是Struts裡的PO對象(持久對象)和對應的ActionForm。例如:一個用戶註冊頁面,有一個User實體類和一個UserActionForm,我們一般會在Action裡從ActionForm構造一個PO對象,傳統的方式是使用類似下面的語句對屬性逐個賦值:

// 獲取 ActionForm 表單數據  
UserActionForm uForm = (UserActionForm) form; 
// 構造一個User對象  
User user = new User();  
// 逐一賦值  
user.setUsername(uForm.getUsername);  
user.setPassword(uForm.getPassword);  
user.setAge(uForm.getAge);    
...........  
...........  
// 然後調用JDBC、或操作Hibernate 持久化對象User到數據庫  
HibernateDAO.save(user); 

通過這樣的方法如果表單數據N多、100、1000(誇張點。哈哈)、、、、那我們不是要寫100、、、1000行set、get瞭。誰都

不願意這樣做。

而我們使用 BeanUtils.copyProperties() 方法以後,代碼量大大的減少,而且整體程序看著也簡潔明朗,代碼如下:

// 獲取 ActionForm 表單數據  
UserActionForm uForm = (UserActionForm) form;      
// 構造一個User對象  
User user = new User();    
BeanUtils.copyProperties(uForm,user);    
// 然後調用JDBC、或操作Hibernate 持久化對象User到數據庫  
HibernateDAO.save(user); 

註:如果User和UserActionForm 間存在名稱不相同的屬性,則BeanUtils不對這些屬性進行處理,需要手動處理。例如:

User類裡面有個createDate 創建時間字段,而UserActionForm裡面無此字段。BeanUtils.copyProperties()不會對此字段做任何處理。必須要自己手動處理。

user.setModifyDate(new Date());  

三、重寫

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類。

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幾毫秒,效率已經沒問題瞭。

BeanUtils.copyProperties 使用註意

首先結論說在前頭, BeanUtils.copyProperties 是淺拷貝 。

為什麼今天我還想把這個BeanUtils.copyProperties 的使用拿出來軍訓。

因為我意識到瞭大傢(部分)對深拷淺拷還是不清晰,不知道具體的影響。

示例演示

第一個類:

第二個類:

註意!! 第二個類裡面有使用第一個類。

開始進行示例

    public static void main(String[] args) { 
        /**
         * 模擬數據 A  complexObject
         */
        ComplexObject complexObjectA=new ComplexObject();
        complexObjectA.setNickName("張一");
        SimpleObject simpleObject=new SimpleObject();
        simpleObject.setName("李四");
        simpleObject.setAge(12);
        complexObjectA.setSimpleObject(simpleObject);  
        /**
         * 使用BeanUtils.copyProperties 拷貝 模擬數據 A 生成模擬數據 B
         */
        ComplexObject complexObjectB=new ComplexObject();
        BeanUtils.copyProperties(complexObjectA,complexObjectB);
 
        System.out.println("拷貝後,查看模擬數據A 和 模擬數據B :");
        System.out.println(complexObjectA.getSimpleObject().toString());
        System.out.println(complexObjectB.getSimpleObject().toString());
 
        System.out.println("比較模擬數據A 和 模擬數據B 裡面的引用對象simple 是否引用地址一樣: ");
        System.out.println(complexObjectA.getSimpleObject()==complexObjectB.getSimpleObject()); 
 
        System.out.println("修改拷貝出來的模擬數據B裡面的引用對象simple的屬性 age 為 888888");
        complexObjectB.getSimpleObject().setAge(888888);
 
        System.out.println("修改後,觀察原數據A 和拷貝出來的數據 B 裡面引用的 對象 simple的屬性 age:");
        System.out.println(complexObjectA.getSimpleObject().toString());
        System.out.println(complexObjectB.getSimpleObject().toString()); 
    }

最後強調

如果你是使用BeanUtils.copyProperties 進行對象的拷貝復制, 一定要註意!

  • 第一點 、你所拷貝的對象內包不包含 其他對象的引用。
  • 第二點、如果包含,那麼接下來的方法裡無論是操作原對象還是操作拷貝出來的對象是否涉及到 對 對象內 的 那個其他對象的 值的修改 。
  • 第三點、如果涉及到, 修改瞭,會不會影響到其他方法 對 修改值的使用情況。

就如文中例子, 如果傳入過來的一個復雜對象數據A 裡面引用瞭一個 user對象年齡age是10;拷貝出一份數據B後, 操作 數據B的方法把 年齡age改成瞭88888;

那麼後續其他方法用到數據A ,想用的是最初始的 age 為10 ,那麼就用不到瞭,因為淺拷貝的原因受影響,age都變成88888 瞭。

ok,就提到這。以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet~

推薦閱讀: