BeanUtils.copyProperties浅拷贝的坑你得知道?
今天想写一篇文章,主要关于深拷贝和浅拷贝相关的,主要是最近写代码的时候遇到一个BUG,刚好涉及到浅拷贝导致的问题。
问题背景
现在有一个需要是需要修改门店信息,门店也区分父门店和子门店,父门店被编辑更新是需要通过到第三方的,然后之前是没有父子门店的概念的,后面新增的需求,然后editShop这个方法的入参就是关于门店的信息么,这里我简化它的参数,但是保留了一个data属于引用型参数。
下面的模拟当时出现BUG的代码
public class RequestDto {Map<String, Object> data = new HashMap<>();private Integer status ;private Long id;public Map<String, Object> getData() {return data;}
}public class ShopService {public void editShop(RequestDto requestDto) {System.out.println("入参:" + requestDto.toString());// 操作// 父门店获取子门店,假设有两个List<Long> childShops = getChildShop(requestDto.getId());childShops.stream().forEach(childShop -> {RequestDto requestDto1 = new RequestDto();BeanUtils.copyProperties(requestDto, requestDto1);requestDto1.setId(childShop);requestDto1.getData().put("isSync", Boolean.FALSE);editShop(requestDto1);sync(childShop);});System.out.println("返回前:" + requestDto.toString());if (Boolean.FALSE.equals(requestDto.getData().get("isSync"))) {return;}sync(requestDto.getId());}private List<Long> getChildShop(Long parentId) {if (parentId.equals(1L)) {// 父门店List<Long> childShopIds = new ArrayList<>();childShopIds.add(123L);childShopIds.add(234L);return childShopIds;} else {return new ArrayList<>();}}private void sync(Long shopId) {System.out.println("同步门店第三方:" + shopId);}
}
public class Main {public static void main(String[] args) {ShopService shopService = new ShopService();RequestDto requestDto = new RequestDto();requestDto.setId(1L);requestDto.setStatus(0);shopService.editShop(requestDto);}}
模拟了当时操作父门店时,需要触发子门店的配置信息同步,通过递归来实现似乎是不错的选择,但是将isSync作为递归结束的标志来,然后就可以完成配置的复制和更新,看起来是没有什么问题的,但是当这个代码真的到达线上的时候,发现编辑父门店,子门店触发通过不了,但是父门店就触发不了同步了,在判断的if里面就退出了,导致线上发现父门店配置没有同步的BUG。然后经过一顿头发输出,看了好几遍日志之后,也没有发现问题的所在,后面本地起了一个调试才发现,这个问题是出自于BeanUtils.copyProperties的问题,排查出来了。
BeanUtils.copyProperties的时候,已经将requestDto1中data的引用指向了requestDto的引用,然后对它添加isSync的时候是会导致父门店的dto也同时被修改了。

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) {// 写入的set方法Method writeMethod = targetPd.getWriteMethod();if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {// source对象对应的的属性PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());if (sourcePd != null) {Method readMethod = sourcePd.getReadMethod();if (readMethod != null) {ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);// Ignore generic types in assignable check if either ResolvableType has unresolvable generics.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);}catch (Throwable ex) {throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", ex);}}}}}}}
然后这里我们可以看到它最后也是通过反射的invoke方法来进行set设置值,而value是从之前的source获取,所以获取的是它的引用,所以用的是浅引用。
浅拷贝和深拷贝
浅拷贝从上面的例子应该是非常容易就可以理解了,就是浅拷贝除了拷贝基础数据类型及其包装类型外,在对引用类型的拷贝时是直接拷引用,在Java中就是相当于是同一个的对象的引用。
深拷贝的话就是相当于创建一个新的一模一样的对象,但是这个对象的内存地址是不一致的。
在Java里面实现深拷贝的方式有三种。
-
通过直接创建赋值,set
-
通过序列化和反序列化创建的对象是属于重新生成的对象,也是属于深拷贝。
-
通过fastjson这样的json反序列化也是实现深拷贝的一种方式。
-
通过实现重写父类的clone方法也可以实现。
借此记录下自己踩到的BUG,虽然不是很难的东西,写出来也是希望下一次遇到这个问题的时候能够更快的解决!
相关文章:
BeanUtils.copyProperties浅拷贝的坑你得知道?
今天想写一篇文章,主要关于深拷贝和浅拷贝相关的,主要是最近写代码的时候遇到一个BUG,刚好涉及到浅拷贝导致的问题。 问题背景 现在有一个需要是需要修改门店信息,门店也区分父门店和子门店,父门店被编辑更新是需要通过…...
ubuntu安装rabbitMQ 并 开启记录消息的日志
apt-get update apt-get install rabbitmq-server rabbitmqctl add_user root password // 设置用户名密码 rabbitmqctl set_user_tags root administrator // 设置为管理员身份 rabbitmqctl set_permissions -p / root ".*" ".*" ".*" //为…...
思维模型 首因效应
本系列文章 主要是 分享 思维模型,涉及各个领域,重在提升认知。先入为主,一见钟情。 1 首因效应的应用 1.1 面试中的首因效应 小李是一名应届毕业生,他准备参加一家知名互联网公司的面试。在面试前,他做了充分的准备…...
Redis极速上手开发手册【Redis全面复习】
文章目录 什么是RedisRedis的特点Redis的应用场景Redis安装部署Redis基础命令Redis多数据库特性Redis数据类型Redis数据类型之stringRedis数据类型之hashRedis数据类型之listRedis数据类型之setRedis数据类型之sorted set案例:存储高一班的学员信息 Redis封装工具类…...
[动态规划] (十四) 简单多状态 LeetCode LCR 091.粉刷房子
[动态规划] (十四) 简单多状态 LeetCode LCR 091.粉刷房子 文章目录 [动态规划] (十四) 简单多状态 LeetCode LCR 091.粉刷房子题目解析解题思路状态表示状态转移方程初始化和填表顺序返回值 代码实现总结 LCR 091. 粉刷房子 题目解析 (1) 一排房子,共有n个 (2) 染…...
【VSS版本控制工具】
VSS版本控制工具 1 安装 VSS2 服务器端配置3 新建用户4 客户端配置Vss2005Vs20055 客户端详细操作 1 安装 VSS 第一步:将VisualSourceSafe2005安装包解压。 第二步:找到setup.exe双击运行。 第三步:在弹出的界面复选框中选中Iaccepttheterms…...
数据持久化技术(Python)的使用
传统数据库连接方式:mysql(PyMySQL)ORM 模型:SQLAlchemy MyBatis、 Hibernate PyMySQL 安装: pip install pymysql简单使用 利用 pymysql.connect 建立数据库连接并执行 SQL 命令(需要提前搭建好数据库…...
第23章(上)_索引原理之索引与约束
文章目录 索引索引分类主键选择索引的代价 约束外键约束约束与索引的区别 索引使用场景不要使用索引的场景总结 索引 索引的概念:索引是一种有序的存储结构。索引按照单个或多个列的值进行排序。 索引的目的:提升搜索效率。 索引分类 按照数据结构分为…...
金蝶云星空BOS设计器中基础资料字段属性“过滤”设置获取当前界面的基础资料值作为查询条件
文章目录 金蝶云星空BOS设计器中基础资料字段属性“过滤”设置获取当前界面的基础资料值作为查询条件背景说明业务需求格式BOS配置 金蝶云星空BOS设计器中基础资料字段属性“过滤”设置获取当前界面的基础资料值作为查询条件 背景说明 序列号档案是基础资料,资料里…...
OFDM深入学习及MATLAB仿真
文章目录 前言一、OFDM 基本原理及概念1、OFDM 简介2、子载波3、符号4、子载波间隔与符号长度之间的关系 二、涉及的技术1、保护间隔2、交织3、信道编码4、扩频5、导频6、RF(射频)调制7、信道估计 三、变量间的关系四、IEEE 802.11a WLAN PHY 层标准五、…...
软件测试简历原来是写了这些才让面试官已读不回
前言: 马上就到了面试跳槽涨薪好时候了,最近看很多的小伙伴已经开始投简历了,一天投了几十次几百次,面试官已读不会,面试的机会都没有更别说后面的事情的,这是为什么呢? 很大一部分的原因是的…...
ESP32网络开发实例-Web服务器RGB LED调光
Web服务器RGB LED调光 文章目录 Web服务器RGB LED调光1、RGB LED介绍3、软件准备4、硬件准备4、代码实现在本文中,我们将创建一个 RGB LED 控制器网络服务器。 Web 服务器将显示用于设置 RGB LED 颜色的色谱。 颜色将主要分为三种:红色、绿色和蓝色。 用户将从光谱中选择一种…...
C# TCP Server服务端多线程监听RFID读卡器客户端上传的读卡数据
本示例使用设备介绍:液显WIFI无线网络HTTP协议RFID云读卡器可编程实时可控开关TTS语-淘宝网 (taobao.com) using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using Sy…...
【electron】【附排查清单】记录一次逆向过程中,fetch无法请求http的疑难杂症(net::ERR_BLOCKED_BY_CLIENT)
▒ 目录 ▒ 🛫 导读需求开发环境 1️⃣ Adblock等插件拦截2️⃣ 【失败】Content-Security-Policy启动服务器json-serverhtml中的meta字段 3️⃣ 【失败】https vs httpwebPreferences & allowRunningInsecureContent disable-features 4️⃣ 【失败】检测fetch…...
【JS】scrollTop+scrollHeight+clientTop+clientHeight+offsetTop+offsetHeight
scrollTop、scrollHeight、clientTop、clientHeight、offsetTop以及offsetHeight 1. scrollTop 与 scrollHeight 1.1 scrollTop scrollTop 是这六个属性中唯一一个可写的属性。 Element.scrollTop 属性可以获取或设置一个元素的内容垂直滚动的像素数。 一个元素的 scrollT…...
Go语言函数用法
文章目录 Go语言函数用法 Go语言函数用法 函数在Go语言中有多种用法,它们是组织和模块化代码、提高代码的可维护性和可重用性的关键部分。以下是函数的一些常见用法: 封装代码:函数允许将一组相关的代码块封装到一个独立的单元中,…...
3.5、Linux:命令行git的使用
个人主页:Lei宝啊 愿所有美好如期而遇 在Linux Centos7.6下安装git yum -y install git 注册一个gitee账号 进去注册就好,记住自己的用户名和密码。 创建一个仓库 点击复制,接着就可以在Linux上使用了 git clone git clone 刚才复制的地…...
基于servlet+jsp+mysql网上书店系统
基于servletjspmysql网上书店系统 一、系统介绍二、功能展示四、其它1.其他系统实现五.获取源码 一、系统介绍 项目类型:Java web项目 项目名称:基于servletjspmysql网上书店系统 项目架构:B/S架构 开发语言:Java语言 前端技…...
自用工具类整理
自动生成数据 uuid&雪花id private static Long workerId 1L; private static Long datacenterId 1L; private static Snowflake snowflake IdUtil.createSnowflake(workerId, datacenterId);public static String getId(String idType) {if (idType.equals("uui…...
jenkins2
jenkins插件管理安装:docker-build jenkins安装了docker 配置docke builder 添加 unix:///var/run/docker.sock rootubuntu20:~# usermod -G docker jenkins 修改docker中service文件添加 -H tcp://0.0.0.0:2376 jenkins中系统管理中 tcp://localhost:2376...
科研人必备:用浏览器插件给IEEEXplore做个‘小手术’,告别20秒加载
科研效率革命:用浏览器插件精准优化IEEEXplore访问体验 每次打开IEEEXplore文献库,那个转不停的加载图标是否让你焦躁不安?作为每天要与学术数据库打交道的科研工作者,20秒的等待时间足以打断思考流,降低工作效率。这背…...
万物识别在智能体(Skills Agent)中的集成应用
万物识别在智能体(Skills Agent)中的集成应用 想象一下,你正在开发一个智能客服机器人,用户发来一张照片,里面是自家厨房水槽下漏水的一堆零件。用户问:“这是什么东西坏了?我该买什么配件?” 传统的文本对…...
Graphormer企业级应用:制药公司分子筛选流水线中的轻量部署实践
Graphormer企业级应用:制药公司分子筛选流水线中的轻量部署实践 1. 项目背景与价值 在药物研发领域,分子筛选是耗时耗力的关键环节。传统实验方法需要数月时间才能完成数千种化合物的性质测试,而基于AI的分子属性预测技术可以将这一过程缩短…...
Qwen3-14B私有部署镜像算法题求解助手:从理解到实现
Qwen3-14B私有部署镜像算法题求解助手:从理解到实现 1. 为什么算法工程师需要AI助手 算法工程师和求职者每天都要面对各种算法问题,从简单的排序到复杂的动态规划。传统方式下,我们需要反复查阅资料、手动编写测试用例、调试代码࿰…...
SmolVLA开发环境搭建:从操作系统安装到模型运行的完整路径
SmolVLA开发环境搭建:从操作系统安装到模型运行的完整路径 如果你刚拿到一台新电脑,或者想把旧机器彻底清理干净,从头开始搭建一个能跑SmolVLA模型的环境,那这篇文章就是为你准备的。很多教程都假设你已经有了一个可用的系统&…...
警惕!新型U盘蠕虫伪装文档传播:实测火绒5.0查杀+防御全攻略
深度解析U盘蠕虫病毒:从防御到查杀的全面安全指南 1. 新型U盘蠕虫病毒的运作机制剖析 U盘蠕虫病毒近年来呈现出越来越复杂的传播方式和技术手段。这类病毒通常利用Windows系统的自动播放功能(AutoRun.inf)或注册表劫持技术进行传播࿰…...
实战指南:在CentOS 8上部署与配置BIND DNS权威服务器
1. 为什么要在CentOS 8上搭建DNS服务器? 想象一下这样的场景:公司内部有几十台服务器,每次新同事入职都要发一份IP地址对照表;开发团队每次联调测试都要反复确认服务地址;运维人员排查问题时要在记事本里翻找各种192.1…...
为什么选择Practical Modern JavaScript:探索ES6未来发展方向
为什么选择Practical Modern JavaScript:探索ES6未来发展方向 【免费下载链接】practical-modern-javascript 🏊 Dive into ES6 and the future of JavaScript 项目地址: https://gitcode.com/gh_mirrors/pr/practical-modern-javascript Practic…...
别再手动画点阵了!用PCtoLCD2002搞定LCD/OLED汉字显示,附STM32移植代码
嵌入式开发实战:PCtoLCD2002字模生成与STM32显示全链路解析 在嵌入式设备上实现中文显示一直是开发者面临的经典难题。传统的手动绘制点阵方式不仅效率低下,而且难以保证显示效果的一致性。本文将深入探讨如何利用PCtoLCD2002工具链,从字模生…...
DeEAR语音情感三维建模:如何用DeEAR输出可量化的Arousal-Nature-Prosody指标
DeEAR语音情感三维建模:如何用DeEAR输出可量化的Arousal-Nature-Prosody指标 1. 语音情感分析的新维度 传统语音情感识别系统通常只能识别"喜怒哀乐"等基础情绪,而DeEAR(Deep Emotional Expressiveness Recognition)系统通过wav2vec2深度学习…...
