4

java bean拷贝操作又一个非常好用的工具类 BeanUitls :
spring (org.springframework.beans.BeanUtils)和apache commons-beanutils(org.apache.commons.beanutils.BeanUtils)中分别存在一个BeanUtils,提供了对。
特别注意 这两个类在不同的包下面,而这两个类的copyProperties()方法里面传递的参数赋值是相反的。
例如:
a,b为对象
BeanUtils.copyProperties(a, b);

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

之前在写程序时,用到了两个不同类型但属性基本相同的对象的拷贝,由于类型不同源bean里属性(Integer 向 int 拷贝)其值为null,这时会抛异常。

 /**
     * 测试 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的实现。

spring中实现的方式很简单,就是对两个对象中相同名字的属性进行简单get/set,仅检查属性的可访问性。

源码

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。然后注册进去。所以在使用时

感觉commons-beanutils包中的这个BeanUtils类的copyProperties方法,太过复杂,约束太多,而且使用不便,虽然可扩展性好了,但是易用性不高。

总结:

关于bean复制,如果属性较少,建议直接写个方法完成get/set即可。如果属性较多,可以自己采用反射实现一个满足自己需要的工具类,或者使用spring的那个beanutils类,不建议使用commons-beanutils包中的那个BeanUtils类,刚看了下,这个类对于内部静态类的对象复制也会出现问题,检验太复杂了,常会出现一些诡异的问题。毕竟我们bean复制一般就是简单的属性copy而已。

而且,由于这些BeanUtils类都是采用反射机制实现的,对程序的效率也会有影响。因此,慎用BeanUtils.copyProperties!!!或者使用clone看效果如何!


kevin
215 声望28 粉丝

stay hungry stay foolish