使用BeanUitls提高对象拷贝效率

对象拷贝

对象拷贝分为深拷贝和浅拷贝。根据使用场景进行不同选择。在Java中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。

深度拷贝和浅度拷贝的主要区别在于是否支持引用类型的属性拷贝,本文将探讨目前使用较多的几种对象拷贝的方案,以及其是否支持深拷贝和性能对比。

关于深拷贝和浅拷贝的理解可以参考:https://www.jianshu.com/p/e8c6155d9694

首先来创建两个测试bean

注:一定要保证有set/get方法,成员变量必须要同名

@Data
public class User1 {
    String name;
    String password;
    String phone;
}
@Data
public class User2 {
    String name;
    String password;
    String phone;
}

1.Spring的BeanUtils(简单易用)

org.springframework.beans.BeanUtils
BeanUtils.copyProperties(源对象,目标对象)

测试方法:
public static void main(String[] args){
        User1 user1=new User1();
        user1.setName("user1_name");
        user1.setPassword("user1_password");
        user1.setPhone("user1_phone");
        User2 user2=new User2();
        BeanUtils.copyProperties(user1,user2);
        System.out.println(user2.toString());
    }

执行结果:User2(name=user1_name, password=user1_password, phone=user1_phone)

实现原理

BeanUtils部分源码分析

public abstract class BeanUtils {
      public static void copyProperties(Object source, Object target) throws BeansException {
        copyProperties(source, target, (Class)null, (String[])null);
      }

      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;
        PropertyDescriptor[] var7 = targetPds;
        int var8 = targetPds.length;

        for(int var9 = 0; var9 < var8; ++var9) {
            PropertyDescriptor targetPd = var7[var9];
            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 var15) {
                            throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15);
                        }
                    }
                }
            }
        }

    }
    ``````
}

实现的方式很简单,就是对两个对象中相同名字的属性进行简单get/set,仅检查属性的可访问性。可以看到, 成员变量赋值是基于目标对象的成员列表, 并且会跳过ignore的以及在源对象中不存在的, 所以这个方法是安全的, 不会因为两个对象之间的结构差异导致错误

注:必须保证同名的两个成员变量类型相同,同名属性一个是包装类型,一个是非包装类型也是可以的

2.Apache的BeanUtils(拓展性强,相对复杂)

Apache Common BeanUtil是一个常用的在对象之间复制数据的工具类,web开发框架struts就是依赖于它进行ActionForm的创建。
org.apache.commons.beanutils.BeanUtils
BeanUtils.copyProperties(目标对象,源对象)

需要引入依赖

        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.3</version>
        </dependency>
测试方法:
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        User1 user1=new User1();
        user1.setName("user1_name");
        user1.setPassword("user1_password");
        user1.setPhone("user1_phone");
        User2 user2=new User2();
        BeanUtils.copyProperties(user2,user1);
        System.out.println(user2.toString());
    }

执行结果:User2(name=user1_name, password=user1_password, phone=user1_phone)

实现原理

BeanUtils部分源码分析

public class BeanUtils {
    public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
        BeanUtilsBean.getInstance().copyProperties(dest, orig);
    }

    public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
        if (dest == null) {
            throw new IllegalArgumentException("No destination bean specified");
        } else if (orig == null) {
            throw new IllegalArgumentException("No origin bean specified");
        } else {
            if (this.log.isDebugEnabled()) {
                this.log.debug("BeanUtils.copyProperties(" + dest + ", " + orig + ")");
            }

            int i;
            String name;
            Object value;
            if (orig instanceof DynaBean) {
                DynaProperty[] origDescriptors = ((DynaBean)orig).getDynaClass().getDynaProperties();

                for(i = 0; i < origDescriptors.length; ++i) {
                    name = origDescriptors[i].getName();
                    if (this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {
                        value = ((DynaBean)orig).get(name);
                        this.copyProperty(dest, name, value);
                    }
                }
            } else if (orig instanceof Map) {
                Iterator entries = ((Map)orig).entrySet().iterator();

                while(entries.hasNext()) {
                    Entry entry = (Entry)entries.next();
                    name = (String)entry.getKey();
                    if (this.getPropertyUtils().isWriteable(dest, name)) {
                        this.copyProperty(dest, name, entry.getValue());
                    }
                }
            } else {
                PropertyDescriptor[] origDescriptors = this.getPropertyUtils().getPropertyDescriptors(orig);

                for(i = 0; i < origDescriptors.length; ++i) {
                    name = origDescriptors[i].getName();
                    if (!"class".equals(name) && this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {
                        try {
                            value = this.getPropertyUtils().getSimpleProperty(orig, name);
                            this.copyProperty(dest, name, value);
                        } catch (NoSuchMethodException var7) {
                        }
                    }
                }
            }

        }
    }
    ``````
}

commons-beanutils则施加了很多的检验,包括类型的转换,甚至于还会检验对象所属的类的可访问性。BeanUtils能够顺利的完成对象属性值的复制,依赖于其对类型的识别。

除了支持基本类型以及基本类型的数组之外,还支持java.sql.Date,java.sql.Time, java.sql.TimeStamp, java.io.File, javaio.URL这些类的对象,其余一概不支持。不过可以自定义Converter。然后注册进去。在org.apache.commons.beanutils.converters包中有一系列converter类,用于不同类型之间对象的转化。主要通过注入新的类型转换器,因为默认情况下,BeanUtils对复杂对象的复制是引用

注:commons-beanutils中的装换是不支持java.util.Date的。

BeanUtils的官方API文档:https://commons.apache.org/proper/commons-beanutils/apidocs/org/apache/commons/beanutils/BeanUtils.html
converter的官方API文档:https://commons.apache.org/proper/commons-beanutils/apidocs/org/apache/commons/beanutils/Converter.html
BeanUtils自定义的转换器可以参考
https://blog.csdn.net/marksinoberg/article/details/51830076
https://blog.csdn.net/qq_14945847/article/details/77450222

总结:关于bean复制,如果属性较少,建议直接写个方法完成get/set即可。如果属性较多,可以自己采用反射实现一个满足自己需要的工具类,或者使用BeanUtils类。BeanUtils是利用反射机制对JavaBean的属性进行处理。一个JavaBean通常包含了大量的属性,很多情况下,对JavaBean的处理导致大量get/set代码堆积,增加了代码长度和阅读代码的难度。由于这些BeanUtils类都是采用反射机制实现的,对程序的效率会有影响。

推荐阅读更多精彩内容