Apache-BeanUtils VS SpringBean-Utils

目录

一、前言

二、对象拷贝

三、什么是浅拷贝和深拷贝

四、BeanUtils

1. Apache 的 BeanUtils

2. Spring 的 BeanUtils

五、性能比较

六、推荐使用

七、深拷贝需求

1. SerializationUtils.clone()

2.SerializableFunction+Iterables.transform()


一、前言

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

二、对象拷贝

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

三、什么是浅拷贝和深拷贝

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

而浅拷贝和深拷贝就是在这个基础上做的区分。

  • 如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝

  • 反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝

简单来说:

  • 浅拷贝:浅拷贝只复制了对象的引用,使得新旧对象指向同一块内存区域。

  • 深拷贝: 对深拷贝则复制了对象的所有数据,包括引用类型的数据,创建了完全独立的新对象。

四、BeanUtils

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

1. Apache 的 BeanUtils

Apache BeanUtils 是Apache Commons库的一部分,它提供了一系列工具方法来简化JavaBean的操作,包括属性读取、设置以及属性拷贝。当使用BeanUtils.copyProperties()方法时,它执行的是浅拷贝,即对于引用类型的属性,拷贝的是引用本身,而不是引用的对象。这意味着如果源对象和目标对象有相同的引用类型属性,它们会指向同一个对象实例。

首先来看一个非常简单的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 对于对象拷贝加了很多的检验,包括类型的转换,甚至还会检验对象所属的类的可访问性,可谓相当复杂,这也造就了它的差劲的性能,具体实现代码如下:

/*** 将一个JavaBean的所有属性复制到另一个JavaBean。* 此方法将从`orig`对象复制属性到`dest`对象,并在必要时进行类型转换。* 它支持从DynaBeans、Maps和标准JavaBeans复制属性。** @param dest 目标bean,属性将被复制到此处。* @param orig 源bean,属性将从此处复制。* @throws IllegalAccessException 如果访问字段或方法时出现问题。* @throws InvocationTargetException 如果底层方法抛出异常。*/
public void copyProperties(final Object dest, final Object orig)throws IllegalAccessException, InvocationTargetException {// 验证目标bean和源bean都不为nullif (dest == null) {throw new IllegalArgumentException("未指定目标bean");}if (orig == null) {throw new IllegalArgumentException("未指定源bean");}if (log.isDebugEnabled()) {log.debug("BeanUtils.copyProperties(" + dest + ", " + orig + ")");}// 根据源bean的类型复制属性if (orig instanceof DynaBean) {// 当源bean是DynaBean时的处理final DynaProperty[] origDescriptors = ((DynaBean) orig).getDynaClass().getDynaProperties();for (DynaProperty origDescriptor : origDescriptors) {final String name = origDescriptor.getName();// 检查是否可以从源读取属性并且可以写入目标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) {// 当源bean是Map时的处理@SuppressWarnings("unchecked")final 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 { // 当源bean是标准JavaBean时的处理final PropertyDescriptor[] origDescriptors = getPropertyUtils().getPropertyDescriptors(orig);for (PropertyDescriptor origDescriptor : origDescriptors) {final String name = origDescriptor.getName();if ("class".equals(name)) {continue; // 跳过复制'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) {// 这不应该发生,因为我们之前检查了可读性}}}}
}

2. Spring 的 BeanUtils

Spring框架中的BeanUtils提供了与Apache BeanUtils类似的功能,但它通常被认为在性能上优于Apache BeanUtils。Spring的BeanUtils.copyProperties()同样执行浅拷贝,但是它的实现更为简洁和高效,因为它直接调用getter和setter方法来进行属性的拷贝,避免了Apache 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,仅检查属性的可访问性。具体实现如下:

/*** 复制源对象的属性值到目标对象中。* <p>注意:源和目标类不需要匹配或相互继承,只要属性名和类型匹配即可。* 不论源对象中是否存在而目标对象中不存在的属性,都将被忽略。* <p>自Spring Framework 5.3起,此方法在匹配源和目标对象的属性时会考虑泛型信息。* 请参阅{@link #copyProperties(Object, Object)}的文档以了解详细信息。* * @param source 源对象,其属性值将被复制。* @param target 目标对象,将接收源对象的属性值。* @param editable 限制属性设置的目标类(或接口)。如果为null,则不限制。* @param ignoreProperties 要忽略的属性名数组。如果为null,则不忽略任何属性。* @throws BeansException 如果复制过程中发生错误。* @see BeanWrapper*/
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");// 如果提供了editable类,则检查目标对象是否是其实例。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) {// 使用ResolvableType检查源和目标属性类型的兼容性。ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);// 检查类型是否兼容,如果存在未解析的泛型则退回到原始的Class.isAssignableFrom检查。boolean isAssignable =(sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ?ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :targetResolvableType.isAssignableFrom(sourceResolvableType));// 如果类型兼容,则尝试复制属性值。if (isAssignable) {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);}// 抛出BeansException封装复制过程中的任何异常。catch (Throwable ex) {throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", ex);}}}}}}
}

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

五、性能比较

根据历史数据,Spring BeanUtils在性能测试中表现得更好,特别是在处理大量对象拷贝时。这是因为Spring BeanUtils的实现更为直接,而Apache BeanUtils在拷贝时会进行更多的检查和转换,这增加了开销。

六、推荐使用

鉴于Spring BeanUtils的性能优势,以及阿里巴巴Java开发规约中明确建议避免使用Apache BeanUtils,通常更推荐使用Spring BeanUtils进行对象属性的拷贝。此外,Spring BeanUtils与Spring框架的集成更加紧密,如果你的应用正在使用Spring,那么使用Spring BeanUtils会更加自然和高效。

七、深拷贝需求

如果你需要深拷贝(即复制引用类型属性所指向的对象),那么Apache BeanUtils和Spring BeanUtils都不是最佳选择。对于深拷贝的需求,可以考虑以下方式:

1. SerializationUtils.clone()

Apache Commons Lang Apache Commons Lang库提供了一个CloneUtils类,可以用于深拷贝对象。但是请注意,CloneUtils在Apache Commons Lang 3.5版本之后被标记为已弃用,建议使用SerializationUtils.clone()方法,该方法使用序列化实现深拷贝。

   import org.apache.commons.lang3.SerializationUtils;T clonedObject = SerializationUtils.clone(originalObject);
​//示例List<AutoCalculationRule> rules = SerializationUtils.clone((ArrayList<AutoCalculationRule>) autoCalculationDefinition.getRules());
   

2.SerializableFunction+Iterables.transform()

Google Guava Google Guava库提供了SerializableFunction和Iterables.transform()方法,可以结合使用实现深拷贝,但通常也需要对象实现Serializable接口。

实现:利用SerializableFunction来封装序列化和反序列化的逻辑,然后使用Iterables.transform()来应用这个函数到集合的每个元素上。

import com.google.common.base.Function;import com.google.common.collect.Iterables;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;
public class DeepCopyWithGuava {/*** 使用Java序列化机制实现单个对象的深拷贝。* * @param original 原始对象,必须实现Serializable接口。* @param <T>      泛型类型参数,限定为实现了Serializable的类型。* @return 返回深拷贝后的对象。*/public static <T extends Serializable> T deepCopy(T original) {try {// 创建一个字节数组输出流,用于存储序列化后的对象数据。ByteArrayOutputStream baos = new ByteArrayOutputStream();// 创建一个对象输出流,用于将对象写入字节数组输出流。ObjectOutputStream oos = new ObjectOutputStream(baos);// 将原始对象写入输出流,完成序列化。oos.writeObject(original);// 刷新输出流,确保所有数据都被写出。oos.flush();// 使用字节数组输入流读取序列化后的对象数据。ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());// 创建一个对象输入流,用于从字节数组输入流中读取对象。ObjectInputStream ois = new ObjectInputStream(bais);// 从输入流中读取对象,完成反序列化,得到深拷贝后的对象。return (T) ois.readObject();} catch (IOException | ClassNotFoundException e) {// 如果在序列化或反序列化过程中发生错误,抛出运行时异常。throw new RuntimeException("Error during deep copy", e);}}
​/*** 使用Guava的Iterables.transform()方法和自定义的SerializableFunction实现对象集合的深拷贝。* * @param originalIterable 原始对象集合,其中的元素必须实现Serializable接口。* @param <T>              泛型类型参数,限定为实现了Serializable的类型。* @return 返回一个包含深拷贝后对象的集合。*/public static <T extends Serializable> Iterable<T> deepCopyIterable(Iterable<T> originalIterable) {// 定义一个SerializableFunction,使用deepCopy方法将对象转换为其深拷贝版本。Function<T, T> deepCopyFunction = DeepCopyWithGuava::deepCopy;// 使用Iterables.transform()方法将deepCopyFunction应用于originalIterable中的每个元素,// 得到一个包含深拷贝后对象的新集合。return Iterables.transform(originalIterable, deepCopyFunction);}// 测试public static void main(String[] args) {// 示例对象class Example implements Serializable {int i;String s;Example(int i, String s) {this.i = i;this.s = s;}}
​// 创建一个Example对象的集合Iterable<Example> examples = new ArrayList<>();examples.add(new Example(1, "one"));examples.add(new Example(2, "two"));
​// 使用Guava进行深拷贝Iterable<Example> copiedExamples = deepCopyIterable(examples);
​// 输出验证for (Example example : copiedExamples) {System.out.println(example.i + ": " + example.s);}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/47807.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于电鸿(电力鸿蒙)的边缘计算网关,支持定制

1 产品信息 边缘计算网关基于平头哥 TH1520 芯片&#xff0c;支持 OpenHarmony 小型系统&#xff0c;是 连接物联网设备和云平台的重要枢纽&#xff0c;可应用于城市基础设施&#xff0c;智能工厂&#xff0c;智能建筑&#xff0c;营业网点&#xff0c;运营 服务中心相关场…

synergy配置

今天介绍一个电脑同步软件synergy。 我们开发时一般会用两套设备&#xff0c;如果使用两套键盘操作起来会很麻烦&#xff0c;这个软件就是解决这个问题&#xff0c;可以使用一套键盘同时操作两台电脑&#xff0c;另一台作为客户端被控制。 安装 在两台电脑上各自下载安装syne…

深度学习中的.pt和.pth文件的区别

深度学习中的.pt和.pth文件的区别 什么是.pt和.pth文件&#xff1f;.pt文件.pth文件 什么是.pt和.pth文件&#xff1f; 首先&#xff0c;.pt和.pth文件都是用于保存PyTorch模型的文件格式。它们可以用于保存模型的权重、结构以及其他相关信息。 .pt文件 .pt文件是PyTorch模型…

使用IDEA编写lua脚本并运行

下载lua https://github.com/rjpcomputing/luaforwindows/releases 是否创建桌面快捷方式&#xff1a;我们的目标是使用IDEA编写lua脚本&#xff0c;所以不需要勾选。后面需要的话&#xff0c;可以到安装目录下手动创建快捷方式 环境变量自动配置 安装后会自动配置好环境变量…

轻量级文本编辑器 | Notepad-- v2.17 官方版

软件简介 Notepad--是一款国产的跨平台轻量级文本编辑器&#xff0c;旨在作为 Notepad 的替代品。它使用 C 编写&#xff0c;支持 Windows、Mac、Linux 等多种操作系统。 鉴于某些Notepad竞品作者的不当言论&#xff0c;Notepad--的意义在于&#xff1a;减少一点错误言论&…

持续集成08--Jenkins邮箱发送构建信息及测试报告

前言 在持续集成&#xff08;CI&#xff09;和持续部署&#xff08;CD&#xff09;的自动化流程中&#xff0c;及时通知团队成员关于构建的成功或失败是至关重要的。Jenkins&#xff0c;作为强大的CI/CD工具&#xff0c;提供了多种通知机制&#xff0c;其中邮件通知是最常用且有…

拖拽上传(预览图片)

需求 点击上传图片&#xff0c;或直接拖拽图片到红色方框里面也可上传图片&#xff0c;上传后预览图片 效果 实现 <!DOCTYPE html> <html lang"zh-cn"><head><meta charset"UTF-8"><meta name"viewport" content&…

Unity动画系统(4)

6.3 动画系统高级1-1_哔哩哔哩_bilibili p333- 声音组件添加 using System.Collections; using System.Collections.Generic; using UnityEngine; public class RobotAnimationController : MonoBehaviour { [Header("平滑过渡时间")] [Range(0,3)] publ…

爬虫瑞数5案例:某大学总医院

声明: 该文章为学习使用,严禁用于商业用途和非法用途,违者后果自负,由此产生的一切后果均与作者无关 一、瑞数简介 瑞数动态安全 Botgate(机器人防火墙)以“动态安全”技术为核心,通过动态封装、动态验证、动态混淆、动态令牌等技术对服务器网页底层代码持续动态变换,…

LLM(大语言模型)解码时是怎么生成文本的?

Part1配置及参数 transformers4.28.1 源码地址&#xff1a;transformers/configuration_utils.py at v4.28.1 huggingface/transformers (github.com) 文档地址&#xff1a;Generation (huggingface.co) 对于生成任务而言&#xff1a;text-decoder, text-to-text, speech-…

vue使用x6画流程图,简单使用

官网 https://x6.antv.antgroup.com/tutorial/getting-started 安装 npm install antv/x6 --save 使用 <template><div>3333<div id"container" style"width: 800px;height: 800px;"></div></div> </template> <…

tg小程序前端-dogs前端源码分析

tg小程序前端-dogs前端源码分析 前端源码 index.html <!DOCTYPE html> <html lang="en"><head><script src="https://telegram.org/js/telegram-web-app.js" onload="window.Telegram.WebApp.expand(); window.Telegram.WebA…

网络安全----防御----防火墙双机热备

实验要求&#xff1a; 1&#xff0c;对现有网络进行改造升级&#xff0c;将当个防火墙组网改成双机热备的组网形式&#xff0c;做负载分担模式&#xff0c;游客区和DMZ区走FW4&#xff0c;生产区和办公区的流量走FW1 2&#xff0c;办公区上网用户限制流量不超过100M&#xff0…

WPF/C#:实现导航功能

前言 在WPF中使用导航功能可以使用Frame控件&#xff0c;这是比较基础的一种方法。前几天分享了wpfui中NavigationView的基本用法&#xff0c;但是如果真正在项目中使用起来&#xff0c;基础的用法是无法满足的。今天通过wpfui中的mvvm例子来说明在wpfui中如何通过依赖注入与M…

第三篇 Vue项目目录结构介绍

1、最外层目录结构 passagerFrontPage ├── .vscode //vscode配置&#xff0c;不用理会 ├── node_modules //项目依赖&#xff0c;npm install命令执行后自动生成 ├── public //公共资源存放 ├── src //源码 ├── tests //选装&#xff1a;测试模块 ├── .git…

最新开源的PDF版面分析工具 PDF-Extract-Kit

最近有一个新开源的版面分析的模型&#xff0c;做PDF版面分析效果非常好。而且对公式的解析效果比较好。虽然现在star数量不高&#xff0c;但是绝对会涨起来的。我们调研对比过很多开源的工具&#xff0c;效果都强差人意&#xff0c;这个是我看到的最满意的一个。甚至要比我们生…

华为云认证

华为云认证 首页 云原生 DevOps工作级开发者认证&#xff1a;HCCDP – Cloud Native DevOps 对云上敏捷开发感兴趣的人员&#xff0c;培训DevOps的理论知识及在云端交付软件全生命周期的实操能力。

使用 XPath 定位 HTML 中的 img 标签

引言 随着互联网内容的日益丰富&#xff0c;网页数据的自动化处理变得愈发重要。图片作为网页中的重要组成部分&#xff0c;其获取和处理在许多应用场景中都显得至关重要。例如&#xff0c;在社交媒体分析、内容聚合平台、数据抓取工具等领域&#xff0c;图片的自动下载和处理…

Springboot 启动时Bean的创建与注入-面试热点-springboot源码解读-xunznux

Springboot 启动时Bean的创建与注入&#xff0c;以及对应的源码解读 文章目录 Springboot 启动时Bean的创建与注入&#xff0c;以及对应的源码解读构建Web项目流程图&#xff1a;堆栈信息&#xff1a;堆栈信息简介堆栈信息源码详解1、main:10, DemoApplication (com.xun.demo)2…

【开发踩坑】使用PageHelper工具正常sql后面多无关语句

背景 SQL日志打印出现了脏东西&#xff1a; 本来结束的 where muc.code ?;后面凭空多出了一个 LIMIT语句 ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your …