点击上方“方志朋”,选择“设为星标”

回复”666“获取新整理的面试文章

来源 | urlify.cn/vUfIry

前言

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

这种转换最原始的方式就是手动编写大量的 get/set代码,当然这是我们开发过程不愿意去做的,因为它确实显得很繁琐。为了解决这一痛点,就诞生了一些方便的类库,常用的有 apache的 BeanUtils,spring的 BeanUtilsDozer,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 beansif (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 necessaryif (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方法进行拷贝,只不过它的实现方式非常简单,就是对两个对象中相同名字的属性进行简单的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,或者使用其他拷贝框架,比如 cglib BeanCopier,基于javassist的Orika等,这些也是非常优秀的类库,值得去尝试,并且也有人去评测过这些Bean映射工具,具体分析请参阅: 常见Bean映射工具分析评测及Orika介绍

热门内容:因用了Insert into select语句,美女同事被开除了!
我们已经不用AOP做操作日志了!Spring Boot+JWT+Shiro+MyBatisPlus 实现 RESTful 快速开发后端脚手架
MyBatis动态SQL(认真看看, 以后写SQL就爽多了)最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・。)ノ♡

BeanUtils 是用 Spring 的还是 Apache 的好?相关推荐

  1. Spring Security 和 Apache Shiro

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 前言 web应用达到生产需要就必须有安全控制.java web领域经常提及的两大开源框架主要 ...

  2. apache ignite_使用Spring Data的Apache Ignite

    apache ignite Spring Data提供了一种统一而简便的方法来访问不同类型的持久性存储,关系数据库系统和NoSQL数据存储. 它位于JPA之上,添加了另一层抽象并定义了基于标准的设计以 ...

  3. 使用Spring Data的Apache Ignite

    Spring Data提供了一种统一而简单的方法来访问不同类型的持久性存储,关系数据库系统和NoSQL数据存储. 它位于JPA之上,添加了另一层抽象并定义了基于标准的设计以在Spring上下文中支持持 ...

  4. 2.24. Spring boot with Apache Kafka

    Spring boot 1.5.1 2.24.1. 安装 kafka 一下安装仅仅适合开发环境,生产环境请使用这个脚本安装 https://github.com/oscm/shell/tree/mas ...

  5. 基于Spring Boot应用Apache CXF发布Web Services服务

    记录:298 场景:使用Spring Boot应用Apache CXF发布Web Services服务,实现跨系统之间交互接口. 版本: JDK 1.8 Spring Boot 2.6.3 Apach ...

  6. Spring Boot 和Apache Kafka的集成

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 1. 引言 Apache Kafka 是一个分布式的.容错 ...

  7. Spring Boot和Apache Camel

    随着软件世界的发展,正在开发更加复杂的系统,这些系统必须相互集成. 它从SOA开始,然后一直到微服务. 骆驼是我想到的第一大集成工具,因为如今的骆驼springboot是一个非常强大的组合. 第一步是 ...

  8. Spring Cloud Discovery——Apache Zookeeper Discovery

    Apache Zookeeper Discovery 依赖说明:使用Apache Zookeeper进行服务发现. Zookeeper是一个高性能,分布式的,开源分布式应用协调服务.它提供了简单原始的 ...

  9. 鉴权/认证框架Spring Security和Apache Shiro比较

    参考: https://www.cnblogs.com/minxiang-luo/p/12492905.html https://www.javadevjournal.com/spring-boot/ ...

最新文章

  1. PostgreSQL和Kingbase中设置search_path
  2. jQuery 判断鼠标键
  3. 软件技术实习_当您还在学校时,如何获得一流的技术实习机会和技术工作
  4. 调用别人服务器运行本地文件方法
  5. python建立sqlite数据库_5分钟快速入门,用Python做SQLite数据库开发,附代码适合初学...
  6. 趣图图解 SOLID 软件开发原则
  7. 【转】十大抢手的网站压力测试工具
  8. arcgis 去除影像黑色边框(nodata)
  9. shell脚本中 EOF的意思
  10. word文档打不开、损坏了怎么修复
  11. CSS常用函数补充(var、clac、blur、gradient)
  12. 常用电源管理稳压IC一览
  13. 吴军《数学之美》第二版阅读整理
  14. 什么是linux目录挂载,Linux-文件系统挂载:mount的用法
  15. python类和对象基础详解
  16. 浅谈Kubernetes集群内部通信
  17. 物联网终端操作系统 TencentOS Tiny
  18. 芜湖c语言市赛答案,安徽省芜湖市2021版数学中考一模试卷C卷
  19. SAP MM顾问,物流管理人员择业的新方向
  20. iOS判断国内固定电话区号

热门文章

  1. conflicts with existing, non-compatible bean definition of same name and class
  2. C#第一个程序Helloworld
  3. [C#][EF] 添加表添加不进来
  4. 在C#中怎样推断线程当前所处的状态
  5. JS中简单原型的使用
  6. C++类的静态成员详细讲解
  7. Matlab数据的可视化 -- 简易表面图
  8. 【Codeforces】401C Team (01010110...)
  9. matlab中patch命令_matlab 放大平移图形是超出边界问题的处理
  10. 7000 字精华总结,Pandas/Sklearn 进行机器学习之特征筛选,有效提升模型性能