关于BeanUtils是用Spring的还是Apache的好

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

这种转换最原始的方式就是手动编写大量的get/set代码,当然这是我们开发过程不愿意去做的,因为它确实确实很繁琐。为了解决这一痛点,就诞生了一些方便的类库,常用的有阿帕奇的BeanUtils,春天的BeanUtils,Dozer,Orika等拷贝工具。这篇文章主要介绍的Apache的BeanUtils的与春天的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进行属性的复制。

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 

            // 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进行对象拷贝:

`public class 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方法进行复制,只不过它的实现方式非常简单,就是对两个对象中相同的名字的属性进行简单的获取/设置,仅检查属性的可访问性。具体实现如下:

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

      } 

    } 

    } 

  } 

  } 

`

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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容