Spring BeanUtils 的选择


Spring BeanUtils 的选择

两难!到底用 Spring BeanUtils 还是 Apache BeanUtils?

前言

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

这种转换最原始的方式就是手动编写大量的 get/set代码,当然这是我们开发过程不愿意去做的,因为它确实显得很繁琐。为了解决这一痛点,就诞生了一些方便的类库,常用的有 apache 的 BeanUtils,spring 的 BeanUtils, Dozer,Orika等拷贝工具。这篇文章主要介绍 Apache 的 BeanUtils 与 Spring 的 BeanUtils,其他框架后续文章再做介绍

对象拷贝

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

什么是浅拷贝和深拷贝

在 Java 中,除了 基本数据类型之外,还存在 类的实例对象这个引用数据类型,而一般使用 “=” 号做赋值操作的时候,对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际还是指向的同一个对象。

而浅拷贝和深拷贝就是在这个基础上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝

简单来说:

  • 浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝

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

BeanUtils

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

apache 的 BeanUtils

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

public class PersonSource  {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    // getters/setters omiited
}
public class PersonDest {
    private Integer id;
    private String username;
    private Integer age;
    // getters/setters omiited
}
public class 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对复杂对象的复制是引用,这是一种浅拷贝

但是由于 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) {
            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) {
            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);
                }
            }
        } else if (orig instanceof Map) {
            @SuppressWarnings("unchecked")
            final
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Map&nbsp;properties&nbsp;are&nbsp;always&nbsp;of&nbsp;type&nbsp;<String,&nbsp;Object>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Map<String,&nbsp;Object>&nbsp;propMap&nbsp;=&nbsp;(Map<String,&nbsp;Object>)&nbsp;orig;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(final&nbsp;Map.Entry<String,&nbsp;Object>&nbsp;entry&nbsp;:&nbsp;propMap.entrySet())&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;final&nbsp;String&nbsp;name&nbsp;=&nbsp;entry.getKey();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(getPropertyUtils().isWriteable(dest,&nbsp;name))&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;copyProperty(dest,&nbsp;name,&nbsp;entry.getValue());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;else&nbsp;/*&nbsp;if&nbsp;(orig&nbsp;is&nbsp;a&nbsp;standard&nbsp;JavaBean)&nbsp;*/&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;final&nbsp;PropertyDescriptor[]&nbsp;origDescriptors&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getPropertyUtils().getPropertyDescriptors(orig);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(PropertyDescriptor&nbsp;origDescriptor&nbsp;:&nbsp;origDescriptors)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;final&nbsp;String&nbsp;name&nbsp;=&nbsp;origDescriptor.getName();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;("class".equals(name))&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;continue;&nbsp;//&nbsp;No&nbsp;point&nbsp;in&nbsp;trying&nbsp;to&nbsp;set&nbsp;an&nbsp;object's&nbsp;class
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(getPropertyUtils().isReadable(orig,&nbsp;name)&nbsp;&&
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getPropertyUtils().isWriteable(dest,&nbsp;name))&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;final&nbsp;Object&nbsp;value&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getPropertyUtils().getSimpleProperty(orig,&nbsp;name);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;copyProperty(dest,&nbsp;name,&nbsp;value);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;catch&nbsp;(final&nbsp;NoSuchMethodException&nbsp;e)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Should&nbsp;not&nbsp;happen
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;}

spring 的 BeanUtils

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

public&nbsp;class&nbsp;TestSpringBeanUtils&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;throws&nbsp;InvocationTargetException,&nbsp;IllegalAccessException&nbsp;{

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

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

private&nbsp;static&nbsp;void&nbsp;copyProperties(Object&nbsp;source,&nbsp;Object&nbsp;target,&nbsp;@Nullable&nbsp;Class<?>&nbsp;editable,
&nbsp;&nbsp;&nbsp;@Nullable&nbsp;String...&nbsp;ignoreProperties)&nbsp;throws&nbsp;BeansException&nbsp;{

&nbsp;&nbsp;Assert.notNull(source,&nbsp;"Source&nbsp;must&nbsp;not&nbsp;be&nbsp;null");
&nbsp;&nbsp;Assert.notNull(target,&nbsp;"Target&nbsp;must&nbsp;not&nbsp;be&nbsp;null");

&nbsp;&nbsp;Class<?>&nbsp;actualEditable&nbsp;=&nbsp;target.getClass();
&nbsp;&nbsp;if&nbsp;(editable&nbsp;!=&nbsp;null)&nbsp;{
&nbsp;&nbsp;&nbsp;if&nbsp;(!editable.isInstance(target))&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;throw&nbsp;new&nbsp;IllegalArgumentException("Target&nbsp;class&nbsp;["&nbsp;+&nbsp;target.getClass().getName()&nbsp;+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"]&nbsp;not&nbsp;assignable&nbsp;to&nbsp;Editable&nbsp;class&nbsp;["&nbsp;+&nbsp;editable.getName()&nbsp;+&nbsp;"]");
&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;actualEditable&nbsp;=&nbsp;editable;
&nbsp;&nbsp;}
&nbsp;&nbsp;PropertyDescriptor[]&nbsp;targetPds&nbsp;=&nbsp;getPropertyDescriptors(actualEditable);
&nbsp;&nbsp;List<String>&nbsp;ignoreList&nbsp;=&nbsp;(ignoreProperties&nbsp;!=&nbsp;null&nbsp;?&nbsp;Arrays.asList(ignoreProperties)&nbsp;:&nbsp;null);

&nbsp;&nbsp;for&nbsp;(PropertyDescriptor&nbsp;targetPd&nbsp;:&nbsp;targetPds)&nbsp;{
&nbsp;&nbsp;&nbsp;Method&nbsp;writeMethod&nbsp;=&nbsp;targetPd.getWriteMethod();
&nbsp;&nbsp;&nbsp;if&nbsp;(writeMethod&nbsp;!=&nbsp;null&nbsp;&&&nbsp;(ignoreList&nbsp;==&nbsp;null&nbsp;||&nbsp;!ignoreList.contains(targetPd.getName())))&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;PropertyDescriptor&nbsp;sourcePd&nbsp;=&nbsp;getPropertyDescriptor(source.getClass(),&nbsp;targetPd.getName());
&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(sourcePd&nbsp;!=&nbsp;null)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Method&nbsp;readMethod&nbsp;=&nbsp;sourcePd.getReadMethod();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(readMethod&nbsp;!=&nbsp;null&nbsp;&&
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ClassUtils.isAssignable(writeMethod.getParameterTypes()[0],&nbsp;readMethod.getReturnType()))&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()))&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;readMethod.setAccessible(true);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object&nbsp;value&nbsp;=&nbsp;readMethod.invoke(source);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()))&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;writeMethod.setAccessible(true);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;writeMethod.invoke(target,&nbsp;value);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch&nbsp;(Throwable&nbsp;ex)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw&nbsp;new&nbsp;FatalBeanException(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Could&nbsp;not&nbsp;copy&nbsp;property&nbsp;'"&nbsp;+&nbsp;targetPd.getName()&nbsp;+&nbsp;"'&nbsp;from&nbsp;source&nbsp;to&nbsp;target",&nbsp;ex);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;}
&nbsp;}

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

小结

以上简要的分析两种 BeanUtils,因为 Apache 下的 BeanUtils 性能较差,不建议使用,可以使用 Spring 的 BeanUtils, 或者使用其他拷贝框架,比如 cglib BeanCopier, 基于 javassist 的 Orika 等,这些也是非常优秀的类库,值得去尝试,并且也有人去评测过这些 Bean 映射工具,具体分析请参阅: 常见 Bean 映射工具分析评测及 Orika 介绍


文章作者: 韩思远
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 韩思远 !
评论
 上一篇
Linux命令大全 Linux命令大全
Linux命令大全操作系统初识操作系统 操作系统(Operation System,OS) 操作系统的作用 是现代计算机系统中最基本和最重要的系统软件 是配置在计算机硬件上的第一层软件,是对硬件系统的首次扩展 主要作用是管理好硬件设备,
2019-09-01
下一篇 
HTML基础 HTML基础
HTML基础笔记 HTML概述及结构 是一种标记语言,不是编程语言 HTML文档(网页)由HTML标签和纯文本组成 文档和页面:HTML文档一般是指含有HTML源代码的纯文本文档,而页面一般是指被浏览器渲染后的最终画面 一个静态的HTML文
2019-07-01
  目录