BeanUtils工具使用細節

java bean拷貝操做又一個很是好用的工具類 BeanUitls :
spring (org.springframework.beans.BeanUtils)和apache commons-beanutils(org.apache.commons.beanutils.BeanUtils)中分別存在一個BeanUtils,提供了對。
特別注意 這兩個類在不一樣的包下面,而這兩個類的copyProperties()方法裏面傳遞的參數賦值是相反的。
例如:
a,b爲對象
BeanUtils.copyProperties(a, b);java

BeanUtils是org.springframework.beans.BeanUtils,
a拷貝到b
BeanUtils是org.apache.commons.beanutils.BeanUtils,
b拷貝到aspring

以前在寫程序時,用到了兩個不一樣類型但屬性基本相同的對象的拷貝,因爲類型不一樣源bean裏屬性(Integer 向 int 拷貝)其值爲null,這時會拋異常。sql

/**
     * 測試 spring 中的 bean 拷貝
     * @throws Exception
     */
    @org.junit.Test
    public void testBeanCopy() throws Exception {
        
      PersonEntity pe = new PersonEntity();
      pe.setAge(1);
      //pe.setId(1234); // id 爲 Integer 此時爲null
      pe.setName("kevin");
      
      Person person = new Person(); // Person 中的id爲int類型
      
      BeanUtils.copyProperties(pe, person);
      
      System.out.println(person);
        
    }

圖片描述

一個看似簡單的問題困擾了在當時,因而抽空看了一下spring和apache commons-beanutils包中BeanUtils.copyProperties的實現。apache

spring中實現的方式很簡單,就是對兩個對象中相同名字的屬性進行簡單get/set,僅檢查屬性的可訪問性。數組

源碼app

package org.springframework.beans;

private static void copyProperties(Object source, Object target, Class<?> editable, 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);
                        }
                    }
                }
            }
        }
    }

而commons-beanutils則施加了不少的檢驗,包括類型的轉換,甚至於還會檢驗對象所屬的類的可訪問性。工具

源碼測試

package org.apache.commons.beanutils;

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

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

        // Copy the properties, converting as necessary
        if (orig instanceof DynaBean) {
            DynaProperty origDescriptors[] =
                ((DynaBean) orig).getDynaClass().getDynaProperties();
            for (int i = 0; i < origDescriptors.length; i++) {
                String name = origDescriptors[i].getName();
                if (getPropertyUtils().isWriteable(dest, name)) {
                    Object value = ((DynaBean) orig).get(name);
                    copyProperty(dest, name, value);
                }
            }
        } else if (orig instanceof Map) {
            Iterator names = ((Map) orig).keySet().iterator();
            while (names.hasNext()) {
                String name = (String) names.next();
                if (getPropertyUtils().isWriteable(dest, name)) {
                    Object value = ((Map) orig).get(name);
                    copyProperty(dest, name, value);
                }
            }
        } else /* if (orig is a standard JavaBean) */ {
            PropertyDescriptor origDescriptors[] =
                getPropertyUtils().getPropertyDescriptors(orig);
            for (int i = 0; i < origDescriptors.length; i++) {
                String name = origDescriptors[i].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 {
                        Object value =
                            getPropertyUtils().getSimpleProperty(orig, name);
                        copyProperty(dest, name, value);
                    } catch (NoSuchMethodException e) {
                        ; // Should not happen
                    }
                }
            }
        }

    }

並且,commons-beanutils中的裝換是不支持java.util.Date的。除了支持基本類型以及基本類型的數組以外,還支持java.sql.Date, java.sql.Time, java.sql.TimeStamp, java.io.File, javaio.URL這些類的對象,其他一律不支持。不過你能夠自定義你的類的Converter。而後註冊進去。因此在使用時spa

感受commons-beanutils包中的這個BeanUtils類的copyProperties方法,太過複雜,約束太多,並且使用不便,雖然可擴展性好了,可是易用性不高。debug

總結:

關於bean複製,若是屬性較少,建議直接寫個方法完成get/set便可。若是屬性較多,能夠本身採用反射實現一個知足本身須要的工具類,或者使用spring的那個beanutils類,不建議使用commons-beanutils包中的那個BeanUtils類,剛看了下,這個類對於內部靜態類的對象複製也會出現問題,檢驗太複雜了,常會出現一些詭異的問題。畢竟咱們bean複製通常就是簡單的屬性copy而已。

並且,因爲這些BeanUtils類都是採用反射機制實現的,對程序的效率也會有影響。所以,慎用BeanUtils.copyProperties!!!或者使用clone看效果如何!

相關文章
相關標籤/搜索