浅析Java中Apache BeanUtils和Spring BeanUtils的用法

# 前言

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

但是由于 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等等

到此这篇关于浅析Java中Apache BeanUtils和Spring BeanUtils的用法的文章就介绍到这了,更多相关Apache BeanUtils和Spring BeanUtils内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2020-11-11

JSP 开发之Spring BeanUtils组件使用

JSP 开发之Spring BeanUtils组件使用 用于演示的javabean import java.util.Date; public class People { private String name; private int age; private Date birth; public People(String name, int age, Date birth) { super(); this.name = name; this.age = age; this.birth =

java JSP开发之Spring中Bean的使用

java JSP开发之Spring中Bean的使用 在传统的Java应用中,bean的生命周期很简单.使用Java关键字new进行bean实例化,然后bean就可以被使用了,一旦该bean不再使用,Java就自动进行垃圾回收.然而,在Spring中,bean的生命周期就比较复杂了.下面是一个bean装载到Spring应用上下文的过程: 如图所示:在你准备调用bean之前,bean工厂执行了若干启动步骤: 1.Spring对bean进行实例化: 2.Spring将值和bean的引用注入到bean对

JSP 开发之Spring Security详解

JSP 开发之Spring Security详解 前言: spring Security是一个能够为基于Spring的企业应用系统提供描述性安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC(依赖注入,也称控制反转)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作. Spring Security 的前身是 Acegi Security ,是 Spring 项

JSP 开发之Spring Boot 动态创建Bean

JSP 开发之Spring Boot 动态创建Bean 1.通过注解@Import导入方式创建 a.新建MyImportBeanDefinitionRegistrar注册中心 Java代码 import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org

JSP开发之Spring方法注入之替换方法实现

JSP开发之Spring方法注入之替换方法实现 Spring提供了一种替换方法实现的机制,可以让我们改变某个bean某方法的实现.打个比方我们有一个bean,其中拥有一个add()方法可以用来计算两个整数的和,但这个时候我们想把它的实现逻辑改为如果两个整数的值相同则把它们相乘,否则还是把它们相加,在不改变或者是不能改变源码的情况下我们就可以通过Spring提供的替换方法实现机制来实现这一要求. 替换方法实现机制的核心是MethodReplacer接口,其中定义了一个reimplement ()方

JSP 开发之servlet中调用注入spring管理的dao

JSP 开发之servlet中调用注入spring管理的dao 我们用spring的依赖注入可以将dao注入到action中,然后我们就可以直接调用了dao中的方法了,可是servlet不是由spring容器管理,所以在servlet中不能注入dao类,也就不能用dao中的方法. 下面是实现方法: private UserDao userDao; public void init() throws ServletException { super.init(); ServletContext s

JSP 开发之 releaseSession的实例详解

JSP 开发之 releaseSession的实例详解 Hibernate可以实现分页查询,昨天试了一下,分页效果不错.但是发现了一个问题,就是当请求超过20次的时候页面就会卡死.经检查,是卡在分页查询这一块. 应用程序采用struts2 + spring2 + hibernate3架构 连接池配置使用的是c3p0, 最大池大小为20, 很显然是连接池耗尽导致的. 增加连接池大小只是饮鸩止渴,总还有耗尽的时候,必须找到根本原因. Dao类的分页查询方法如下: java 代码  public Li

JavaWeb开发之Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架

简单介绍一下,本框架的基本功能点: Spring:整个框架的主体部分,这个自不用说. SpringMVC:MVC部分我还是比较喜欢Spring的. MyBatis:选型的时候选择这个ORM主要也是考虑其灵活性的问题,毕竟我也不知道,今后会遇到怎样的需求,用Hibernate一来是不太会用,二来,我还是比较喜欢直接写SQL来的简单一点. SpringSecurity:这个主要是安全框架,负责用户登录验证及整站权限分配的相关事项(权限分配真的很有用,这个我就不多说了). EhCache:一个非常流行

Java开发之spring security实现基于MongoDB的认证功能

本文实例讲述了Java开发之spring security实现基于MongoDB的认证功能.分享给大家供大家参考,具体如下: spring security对基于数据库的认证支持仅限于JDBC,而很多项目并非使用JDBC,比如Nosql数据库很多使用的是 Mongo Java Driver,这样就无法用默认的<jdbc-user-service>进行支持认证. 如果项目不是使用JDBC,没么解决办法就是:自己定义一个认证服务. 新建一个CustomUserDetailsService类 这个类

java开发之spring webflow实现上传单个文件及多个文件功能实例

本文实例讲述了java开发之spring webflow实现上传单个文件及多个文件功能.分享给大家供大家参考,具体如下: 上传单个文件 准备 1. 如果你项目中使用了spring security的话,参考上一篇文章,使用上篇的第二种方法,并去掉MultipartFilter(如果有配置的话),否则得不到文件 2. 流程中的变量(如用var标签定义的变量),都需要实现Serializable接口. 实现过程 在pom.xml文件中加入下列依赖: <!-- 支持文件上传 --> <depe