在我们实际项目开发过程中,我们经常需要将不同的两个对象实例进行属性复制,从而基于源对象的属性信息进行后续操作,而不改变源对象的属性信息,比如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. UITextView模拟UITextField 设置Placeholder属性 --董鑫

    由于最近有用到输入框,刚开始考虑的是UITextField,因为它在没有输入的时候可以有提示的Placeholder更能,很人性化,但UITextField只能单行输入,不能跳行,对于一些强迫症的亲来 ...

  2. 走进Task(2):Task 的回调执行与 await

    目录 前言 Task.ContinueWith ContinueWith 的产物:ContinuationTask 额外的参数 回调的容器:TaskContinuation Task.Continue ...

  3. IC应届生40万白菜价!从业多年的资深专家手把手指导你如何选择offer!

    这是IC男奋斗史的第1篇原创 关注公众号[IC男奋斗史],让我们一起撸起袖子加油干! 芯片行业2021年的秋招市场又是风起云涌.高手过招,继OPPO给应届生开出40万白菜价offer之后,平头哥又被爆 ...

  4. hydra安装及破解rdp方法

    hydra暴力破解工具 下载:https://github.com/vanhauser-thc/thc-hydra 安装步骤: ./configure make&&make insta ...

  5. Banmabanma的writeup

    大家好,好久不见,前段时间忙于应付网课和作业,还有这恐怖的疫情,差点就嗝屁了......     好了,接下来我们步入正题,这次我为大家带来攻防世界misc部分Banmabanma的writeup. ...

  6. spyeye手机监控最新版分享下载不用碰对方手机,所有聊天内容,照片等都能看见

    spyeye间谍眼介绍的非常详细,不但可以监控用户收发短信和通话记录,还可远程开启手机听筒,监听手机周围声音,实时监控用户的通话,并且利用GPS功能监测到手机用户所在位置,给用户安全隐私造成极大的威胁 ...

  7. Docker遇到的一些问题和感想

    Docker 是"不可变"架构. 当你希望改变一个服务的时候(比如更新版本.修改配置.开放端口),不允许直接登录到服务器上改变某个文件,而是应该把这个服务整个删掉,然后替换成新的版 ...

  8. 微服务从代码到k8s部署应有尽有系列(九、事务精讲)

    我们用一个系列来讲解从需求到上线.从代码到k8s部署.从日志到监控等各个方面的微服务完整实践. 整个项目使用了go-zero开发的微服务,基本包含了go-zero以及相关go-zero作者开发的一些中 ...

  9. js求质数(1-100)

    //求质数,首先要明白质数的概念:除了本身和1外,其他数都是除不尽的. var zhiShuArr = [1, 2, 3]; //可以很容易的确定了.这三个质数 for (var i = 4; i & ...

  10. Oracle导出/导入数据方法--两种方法(pde格式/dmp格式)

    转至:https://www.cnblogs.com/houbxblogs/articles/13365557.html?ivk_sa=1024320u 1.导出数据方法一(pde格式) 工具→ 导出 ...