在我们实际项目开发过程中,我们经常需要将不同的两个对象实例进行属性复制,从而基于源对象的属性信息进行后续操作,而不改变源对象的属性信息,比如DTO数据传输对象和数据对象DO,我们需要将DO对象进行属性复制到DTO,但是对象格式又不一样,所以我们需要编写映射代码将对象中的属性值从一种类型转换成另一种类型。

对象拷贝

在具体介绍两种 BeanUtils 之前,先来补充一些基础知识。它们两种工具本质上就是对象拷贝工具,而对象拷贝又分为深拷贝和浅拷贝,下面进行详细解释。

什么是浅拷贝和深拷贝

在Java中,除了 基本数据类型之外,还存在 类的实例对象这个引用数据类型,而一般使用 “=”号做赋值操作的时候,对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际还是指向的同一个对象。而浅拷贝和深拷贝就是在这个基础上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。

简单来说:

  • 浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝
  • 深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

BeanUtils

前面简单讲了一下对象拷贝的一些知识,下面就来具体看下两种 BeanUtils 工具

Apache 的 BeanUtils

首先来看一个非常简单的BeanUtils的例子

publicclass PersonSource {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    // getters/setters omiited
}
publicclass PersonDest {
    private Integer id;
    private String username;
    private Integer age;
    // getters/setters omiited
}
publicclass TestApacheBeanUtils {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
       //下面只是用于单独测试
        PersonSource personSource = new PersonSource(1, "pjmike", "12345", 21);
        PersonDest personDest = new PersonDest();
        BeanUtils.copyProperties(personDest,personSource);
        System.out.println("persondest: "+personDest);
    }
}
persondest: PersonDest{id=1, username='pjmike', age=21}

从上面的例子可以看出,对象拷贝非常简单,BeanUtils最常用的方法就是:

//将源对象中的值拷贝到目标对象//将源对象中的值拷贝到目标对象
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
    BeanUtilsBean.getInstance().copyProperties(dest, orig);
}

默认情况下,使用org.apache.commons.beanutils.BeanUtils对复杂对象的复制是引用,这是一种浅拷贝

关注公众号程序员小乐回复关键字“offer”获取算法面试题和答案。

但是由于 Apache下的BeanUtils对象拷贝性能太差,不建议使用,而且在阿里巴巴Java开发规约插件上也明确指出:

Ali-Check | 避免用Apache Beanutils进行属性的copy。

commons-beantutils 对于对象拷贝加了很多的检验,包括类型的转换,甚至还会检验对象所属的类的可访问性,可谓相当复杂,这也造就了它的差劲的性能,具体实现代码如下:

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

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

        // Copy the properties, converting as necessary
        if (orig instanceof DynaBean) {
            final DynaProperty[] origDescriptors =
                ((DynaBean) orig).getDynaClass().getDynaProperties();
            for (DynaProperty origDescriptor : origDescriptors) {
                final String name = origDescriptor.getName();
                // Need to check isReadable() for WrapDynaBean
                // (see Jira issue# BEANUTILS-61)
                if (getPropertyUtils().isReadable(orig, name) &&
                    getPropertyUtils().isWriteable(dest, name)) {
                    final Object value = ((DynaBean) orig).get(name);
                    copyProperty(dest, name, value);
                }
            }
        } elseif (orig instanceof Map) {
            @SuppressWarnings("unchecked")
            final
            // Map properties are always of type <String, Object>
            Map<String, Object> propMap = (Map<String, Object>) orig;
            for (final Map.Entry<String, Object> entry : propMap.entrySet()) {
                final String name = entry.getKey();
                if (getPropertyUtils().isWriteable(dest, name)) {
                    copyProperty(dest, name, entry.getValue());
                }
            }
        } else/* if (orig is a standard JavaBean) */ {
            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
                    }
                }
            }
        }

    }

Spring 的 BeanUtils

使用spring的BeanUtils进行对象拷贝:

publicclass TestSpringBeanUtils {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {

       //下面只是用于单独测试
        PersonSource personSource = new PersonSource(1, "pjmike", "12345", 21);
        PersonDest personDest = new PersonDest();
        BeanUtils.copyProperties(personSource,personDest);
        System.out.println("persondest: "+personDest);
    }
}

Spring下的BeanUtils也是使用 copyProperties方法进行拷贝,只不过它的实现方式非常简单,就是对两个对象中相同名字的属性进行简单的get/set,仅检查属性的可访问性。具体实现如下:

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);
            }
          }
        }
      }
    }
  }

可以看到,成员变量赋值是基于目标对象的成员列表,并且会跳过ignore的以及在源对象中不存在,所以这个方法是安全的,不会因为两个对象之间的结构差异导致错误,但是必须保证同名的两个成员变量类型相同。

小结

以上简要的分析两种BeanUtils,因为Apache下的BeanUtils性能较差,不建议使用,可以使用 Spring的BeanUtils ,或者使用其他拷贝框架,比如:Dozer、ModelMapper

Apache BeanUtils与Spring BeanUtils性能比较的更多相关文章

  1. Bean映射工具之Apache BeanUtils VS Spring BeanUtils

    背景 在我们实际项目开发过程中,我们经常需要将不同的两个对象实例进行属性复制,从而基于源对象的属性信息进行后续操作,而不改变源对象的属性信息,比如DTO数据传输对象和数据对象DO,我们需要将DO对象进 ...

  2. 基于 asm 实现比 spring BeanUtils 性能更好的属性拷贝框架

    Bean-Mapping 日常开发中经常需要将一个对象的属性,赋值到另一个对象中. 常见的工具有很多,但都多少不够简洁,要么不够强大. 我们经常使用的 Spring BeanUtils 性能较好,但是 ...

  3. spring: beanutils.copyproperties将一个对象的数据塞入到另一个对象中(合并对象)

    spring: beanutils.copyproperties将一个对象的数据塞入到另一个对象中(合并对象) 它的出现原因: BeanUtils提供对Java反射和自省API的包装.其主要目的是利用 ...

  4. Spring Boot 性能优化

    spring 框架给企业软件开发者提供了常见问题的通用解决方案,包括那些在未来开发中没有意识到的问题.但是,它构建的 J2EE 项目变得越来越臃肿,逐渐被 Spring Boot 所替代.Spring ...

  5. apache FtpServer 整合spring部署

    我们在项目中可能会出现这样的需求,使用ftp上传很大的文件后对需要对文件进行相应的逻辑处理,这时我们可以使用apache ftpServer来处理这段逻辑,只要我们做相应的部署和编写我们的逻辑代码,这 ...

  6. 使用Apache CXF和Spring集成创建Web Service(zz)

    使用Apache CXF和Spring集成创建Web Service 您的评价:       还行  收藏该经验       1.创建HelloWorld 接口类 查看源码 打印? 1 package ...

  7. [转载]JDBC/Spring/MyBatis性能比较

    原文地址:JDBC/Spring/MyBatis性能比较作者:tom_lt 测试目的: 比较JDBC,SpringJdbc和MyBatis的性能.   测试用例: 1. 查询:查询一张10000条数据 ...

  8. apache shiro整合spring(一)

    apache shiro整合spring 将shiro配置文件整合到spring体系中 方式一:直接在spring的配置文件中import shiro的配置文件 方式二:直接在web.xml中配置sh ...

  9. Apache、nginx 、lighttpd性能比较

    Apache.nginx .lighttpd性能比较 1. web服务器简介 1. lighttpd Lighttpd是一个德国人领导的开源软件,其根本的目的是提供一个专门针对高性能网站,安全.快速. ...

随机推荐

  1. JS快速入门(二)

    目录 JS快速入门(二) 结构图 BOM window对象 open() window子对象 location对象 history对象(了解) navigator 对象 screen对象 BOM 定时 ...

  2. vue/cli的目录结构说明

    node_modules:npm 加载的项目所需要的各种依赖模块. src:这里是我们开发的主要目录(源码),基本上要做的事情都在这个目录里面,里面包含了几个目录及文件: 1.assets:放置一些图 ...

  3. Java两个整数相除保留n位小数

    方式1:被除数转double后,除以除数,结果是一个double类型的数,将double结果按要求保留n位小数即可. 保留n位小数的写法 int a = 10; int b = 3; double r ...

  4. Java中继承时静态块,构造块,构造函数的执行顺序

    public class Father { static { System.out.println("Father静态块"); } { System.out.println(&qu ...

  5. spring mvc通过客户端传值,controller获取Sort对象

    之前客户端需要根据需求按不同的排序方式查看数据,按照一种约定排序,比如1代表时间升序,2代表时间降序,3,4这种形式,然后后台根据这些值创建Sort对象. 后来发现完全多此一举,可以根据特定的方式,直 ...

  6. C# 在PDF中添加墨迹注释Ink Annotation

    PDF中的墨迹注释(Ink Annotation),表现为徒手涂鸦式的形状:该类型的注释,可任意指定形状顶点的位置及个数,通过指定的顶点,程序将连接各点绘制成平滑的曲线.下面,通过C#程序代码介绍如何 ...

  7. 《操作系统导论》第5章 | 进程API

    本章主要讨论UNIX系统中的进程创建.UNIX系统采用了一种非常有趣的创建新进程的方式,即通过一对系统调用:fork()和exec().进程还可以通过第三个系统调用wait(),来等待其创建的子进程执 ...

  8. Rainbond 5.6 版本发布,增加多种安装方式,优化拓扑图操作体验

    Rainbond 5.6 版本,主要致力于提升拓扑图操作效率以及快速安装体验,降低用户使用门槛. 主要功能点解读: 支持单机快速体验 为了方便在单机电脑上快速安装体验Rainbond,当前版本支持通过 ...

  9. 网络测试技术——802.1X原理

    一.以太网优点缺点 1.以太网优点 (1)即插即用,简单快捷 (2)任何一台电脑只要接入网络便有访问网络资源的权限 2.以太网缺点 (1)缺乏安全认证机制(二层) (2)电脑接到交换机上就能访问网络 ...

  10. Docker容器日志的种类以及存储

    Docker 日志 Docker的日志可以分为两类: stdout标准输出日志 stdout就是标准输出里面的日志, 比如程序运行时输出在控制台的内容就会写入标准输出 原理就是当在启动进程的时候,进程 ...