【保姆级】手把手Debug循环依赖的整体流程

news2025/1/24 17:44:45

我们先看一下循环依赖,这样看、

或者这样看、

一提到循环依赖基本必提三级缓存,本篇又是篇保姆级的Debug教程,详解出现循环依赖Spring处理的全过程,之前也介绍过一些与本篇相关的内容:

想瞅瞅三级缓存的庐山真面目可以移步到

【Spring源码】2.试个水先~Debug找到传说中的三级缓存

想了解更多关于三级缓存的相关内容欢迎移步到

【Spring源码】22. 关于循环依赖的N个问题

测试准备

既然要Debug,我们需要先准备几个测试类(包括两个互相依赖的实体类(BeanA.java,BeanB.java),一个配置文件(circulate.xml),还有一个程序启动类(Test.java))

BeanA.java

public class BeanA {

BeanB beanB;

   public BeanB getBeanB () {
return beanB;
   }

public void setBeanB ( BeanB beanB ) {
this.beanB = beanB;
   }
} 
复制代码

BeanB.java

public class BeanB {

BeanA beanA;

   public BeanA getBeanA () {
return beanA;
   }

public void setBeanA ( BeanA beanA ) {
this.beanA = beanA;
   }
} 
复制代码

circulate.xml

 <? xml version="1.0" encoding="UTF-8" ?>
< beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd" >

 < bean id="beanA" class="com.aqin.custom.circulate.BeanA" >
 < property name="beanB" ref="beanB" ></ property >
 </ bean >
 < bean id="beanB" class="com.aqin.custom.circulate.BeanB" >
 < property name="beanA" ref="beanA" ></ property >
 </ bean >


</ beans > 
复制代码

Test.java

public class Test {
public static void main ( String [] args ) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext ( "circulate.xml" ) ;
      BeanA bean = applicationContext.getBean ( BeanA.class ) ;
      System.out.println ( bean ) ;
   }

} 
复制代码

Debug流程

断点位置

Test.java

AbstractApplicationContext.java

AbstractBeanFactory.java

AbstractAutowireCapableBeanFactory.java

正式开撕

启动

由于我们已经在关键步骤上打了断点,可以直接点击Resume按钮🔘进入下一个断点

开始初始化剩下非懒加载的单实例

点击Resume按钮🔘,来到了finishBeanFactoryInitialization()方法

再次点击Resume按钮🔘,来到了preInstantiateSingletons()方法

开始获取循环依赖中的第一个对象

再次点击Resume按钮🔘,来到doGetBean()

点击step into进入getSingleton()方法中

此时,尝试从一级缓存(singletonObjects)中获取BeanA对象

但我们可以看到此时一级缓存(singletonObjects)中并没有BeanA对象,于是接下来的判断singletonObject == null && isSingletonCurrentlyInCreation(beanName)的第一个条件满足了,我们进入isSingletonCurrentlyInCreation()查看第二个条件是否满足

进入后,发现并不满足,直接返回一个空的对象

未获取到、开始创建循环依赖中的第一个对象,并将第一个对象的工厂方法放入三级缓存

既然从缓存中获取不到,那就需要开始创建BeanA对象了,点击Resume按钮🔘,来到createBean()

继续点击Resume🔘,来到doCreateBean(),进入真正创建Bean的逻辑

点击Resume🔘,来到addSingletonFactory()

BeanName和创建该Bean的lambda表达式作为键值对放入三级缓存

由于在方法createBeanInstance()中我们已经对beanA进行了实例化(已经在内存中创建了空间BeanA@1607

循环依赖中第一个对象进行属性填充时获取第二个对象

接下来进行属性填充,于是开始尝试获取BeanB

未获取到、开始创建循环依赖中的第二个对象

BeanB也同样的未能从缓存中获取到,于是开始创建BeanB

来到createBeanInstance()中,此时beanB会进行实例化

随后添加进三级缓存

实例化后的beanB已经在内存中有了空间地址(BeanB@1730)继续进行初始化

循环依赖中的第二个对象进行属性填充时获取第一个对象

接下来调用populateBean()方法进行属性填充,populateBean()方法中向BeanB填充属性BeanA时,调用getBean()方法尝试从缓存中获取BeanA的实例

于是再次来到了doGetBean()中的getSingleton()

getSingleton()中先尝试从一级缓存(singletonObjects)中获取BeanA的实例,没获取到再尝试从二级缓存(earlySingletonObjects)中获取BeanA的实例,还是没获取到,于是来到了三级缓存(singletonFactories

获取到第一个对象存入三级缓存的工厂对象、创建了第一个对象为第二个对象填充属性

可以看到此时的三级缓存中已经不是空的了,在之前的步骤中我们把BeanABeanName和创建BeanA的lambda表达式、BeanBBeanName和创建BeanB的lambda表达式作为键值对都放入了三级缓存,所以此时获取到的工厂对象singletonFactory是不为null的,于是通过该工厂对象创建一个BeanA的单例对象,将这个对象添加进了二级缓存,并将其从三级缓存中移除

回到doGetBean()方法中,此时获取到了BeanA的实例对象BeanA@2065

将获取到的BeanA实例不断返回至刚开始调用getBean()populateBean()方法中,继续执行到最后的赋值方法applyPropertyValues()中,将获取到的实例对象BeanA@2065通过调用setPropertyValues()完成向BeanBbw对象的属性赋值操作

接下来继续调用initializeBean()方法完成实例BeanB@1730的初始化,于是我们获取到了一个完整的BeanB

返回完整的第二个对象完成第一个对象的属性填充

继续下一步会发现我们又回到了getBean()中(b_d)

别懵,还记不记得咱们的debug最先是从哪里开始的(˶‾᷄ ⁻̫ ‾᷅˵)?

自问自答:从实例化BeanA后,对其进行属性填充时,调用getBean()获取BeanABeanB属性开始的

所以,我们又回到了最开始获取BeanB属性的时候,而经过上面的一大串操作,此时返回的是一个完整的BeanB了,于是同样的,像刚刚给BeanBbw对象属性赋值的流程一样,我们进入方法applyPropertyValues()中,将获取到的实例对象BeanB@1730通过调用setPropertyValues()完成向BeanAbw对象的属性赋值操作

执行完填充属性的populateBean()方法后,就可以看到下图中的红框框中的对象,没错、解决了循环依赖的问题

回到启动类,获取到完整的BeanABeanB

Debug完成,撒个花(。・ω・。)ノ🎉🎉

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

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

相关文章

2022/11/27[指针] 指针与函数基础

程序&#xff1a;求10个数的最大数 1、定义指向函数的指针变量调用函数的方法 一般定义形式为&#xff1a;类型名 &#xff08;*指针变量名)()&#xff1b; #include<stdio.h> int main() {int i, m, a[10], max(int* p);int (*f)();for (i 0; i < 10; i)scanf_s(&q…

牛客网基础知识强化巩固-周结03

数组强化训练篇 2022-11-21 打卡 知识点总结 什么是渐进时间复杂度 渐进时间复杂度是指n趋于无穷时的复杂度。向有序表中任意一个位置插入元素&#xff0c;插入位置之后的元素依次挪动一个位置&#xff0c;假设元素插入的位置坐标为k&#xff0c;则时间复杂度为O(k)&#xf…

名词性从句

目录 1.名词性从句 1.1陈述句 1.2一般疑问句变名词性从句 1.3特殊疑问句变名词性从句 1.4特别的what 2.同位语从句 3.同位语从句与定语从句的区别 4.同位语从句的巅峰知识点 5.主语从句 5.2 并列主语从句 5.3主语从句巅峰知识点 6.宾语从句 主系表是不能被动的。所以第…

WPF-控件的常用属性-单例-隧道事件

特殊字符 小于< 大于> 空格xml:space"preserve" 例如&#xff1a;TextBox Grid.Column"1" xml:space"preserve">aaaaa .</TextBox> 从一个xaml文件中获取xaml内容 //DependencyObject是wpf控件的基类&#xff0c;它可以放到…

Ubuntu 安装“安装程序向硬盘复制文件时遇到错误[Errno 5] Input/output error”错误解决过程记录

前阵子装 Ubuntu&#xff0c;原本已经装了几百遍的系统它又双叒叕出问题了&#xff0c;问题内容就如标题看到的那样。解决过程中经历了很多坎坷&#xff0c;不过好在最后还是成功了~ 下面整理了一下我解决问题的过程&#xff0c;需要自取~ 方法 1&#xff1a;烧录 U 盘时文件格…

chapter3——处理多个时钟

目录1.多时钟域2.多时钟域设计的难题3.多时钟设计的处理技术时钟命名规则分模块设计跨时钟域4.跨时钟域同频零相位差时钟同频恒定相位差时钟非同频、可变相位差时钟整数倍频率的时钟非整数倍频率的时钟5.握手信号方法6.使用同步FIFO传输数据7.异步FIFO1.多时钟域 常见的多时钟…

linux环境验证c++程序库间调用

目录 0 背景 1 VMware、以及linux安装 2 安装QtCreator 2.1 下载速度慢&#xff0c;使用国内镜像 2.2 执行有问题&#xff0c;修改权限 2.3 验证是否可用 2.4 调试&#xff08;待解决&#xff09; 3 开发程序进行验证 4 其他问题 4.1 虚拟机与主机无法拷贝文件&#x…

python3-函数与参数以及空值

目录画星星空值None函数与Lambda一、函数定义二、引用变量查找三、传值调用函数四、函数对象五、函数文档旋转角度绘制函数图形画星星 程序2-7-7主要使用turtle.forward前进操作和turtle.left左转操作在屏幕上画星星。 #!/usr/bin/env python3 # -*- coding: utf-8 -*- #2-7-…

操作符详解(1)

目录 一、操作符分类 二、算术操作符 三、位移操作符 1、<< 左位移操作符号 2、>> 右位移操作符 四、位操作符 1、& --- 按位与 2、| --- 按位或 3、^ --- 按位异或 变态题 五、赋值操作符 符合赋值符 一、操作符分类 操作符也被称为运算符。 …

小程序注册安装以及新手快速入门教程

一、注册并安装微信小程序 1.打开 https&#xff1a;//mp.weixin.qq.com/ 网址&#xff0c;点击立即注册即可进入小程序开发账号的注册流程&#xff0c;注册的账号类型选择小程序。 2.根据注册要求注册&#xff0c;发送邮箱信息&#xff0c;接收到微信团队发送的邮箱信息后&am…

水泥行业工业互联网平台(CCPS)解决方案

水泥行业经过过去十年的发展和调整&#xff0c;基本实现了集团化。集团管控当前面临的主要问题是数字的分散化和碎片化&#xff0c;建设工业互联网是新时期加强集团管控的必经之路。 CCPS平台优势 1.融合SOA理念的架构平台和系统框架。具有跨平台、易维护、可集成、可扩展、分…

Spark - 介绍及使用 Scala、Java、Python 三种语言演示

一、Spark Apache Spark 是一个快速的&#xff0c;多用途的集群计算系统&#xff0c; 相对于 Hadoop MapReduce 将中间结果保存在磁盘中&#xff0c; Spark 使用了内存保存中间结果&#xff0c; 能在数据尚未写入硬盘时在内存中进行运算。 Spark 只是一个计算框架, 不像 Hadoo…

几率波量子雷达/反事实量子通信

物理学中有哪些不可思议&#xff08;违背直觉&#xff09;的事实&#xff1f; - 知乎 利用粒子的双缝干涉原理&#xff0c;可以在物体偏离的情况下&#xff0c;探测到物体。 我们不需要用光子照射物体&#xff0c;就能感知到是否有物体存在。 这是什么意思&#xff1f; 这就…

3516DV300 推流

3516DV300 推流 基于ffmpeg将编码后的264文件&#xff08;或者直接推流&#xff09;推流出去&#xff0c;使用ffplay进行播放和验证。 ffmpeg版本&#xff1a;N-109124-g63db6a02a7 RELEASE&#xff1a;5.1.git ffmpeg udp文件推流 命令行 这里用的是开发板编码出的码流&…

MIT 6.S081 Operating System Lecture5 (随意的笔记)

系列文章目录 文章目录系列文章目录TrapsA questionsyscallTraps 用户空间和内核空间的切换通常被称为 trap example: shwrite()ecall(); //write 通过 ecall() 指令执行系统调用之后跳转执行到 usertrap 如下图的执行过程。最终sys_write将要显示的数据输出到 console 上 …

【Java 设计模式】创建者模式 之原型模式

原型模式1 定义2 角色3 三好学生案例3.1 浅克隆实现3.1.1 浅克隆定义3.1.2 类图3.1.3 实现3.2 深克隆实现3.2.1 深克隆定义3.2.2 实现1 定义 将一个已经创建好的实例作为原型&#xff0c;通过复制该原型对象来创建一个和原型对象相同的新对象。 2 角色 抽象原型类&#xff1…

Chrome的使用技巧

1. 请求重发 F12 -> Network -> Replay XHR 2. 修改请求参数后重发 F12 -> Network -> Copy -> Copy as fetch 然后在 Console 控制台 ctrl + v ,而后就可以对请求的参数进行编辑,回车就会重更新请求 再在 Network 就可以看到这个请求了 3. 复制对象 (…

echarts入门到实战

官网地址&#xff1a;Apache ECharts 前言 我们应该经常看到或听到”数据可视化“这个词&#xff0c;他其实就是将数据通过各种图表更加直观的展现变化趋势&#xff0c;对比&#xff0c;峰值等等。数据可视化也是未来的趋势。 作为前端程序员&#xff0c;数据可视化也是我们必…

多任务全景感知YOLOPv2:目标检测、freespace、车道线

今年年初出了一片《端到端的多任务感知网络HybridNet&#xff0c;性能优于YOLOP》&#xff0c;论文 HybridNets: End2End Perception Network&#xff0c;代码已开源&#xff0c;在目标检测、车道线、freespace的多任务感知任务上性能优于YOLOP&#xff0c;取得了新SOTA。视频效…

大厂Java面试必备面试题:基础语法-数据类型-编码-注释-运算符-关键字-流程控制语句

基础语法 数据类型 Java有哪些数据类型 定义&#xff1a;Java语言是强类型语言&#xff0c;对于每一种数据都定义了明确的具体的数据类 型&#xff0c;在内存中分配了不同 大小的内存空间。 分类&#xff1a; 基本数据类型 Java源代码---->编译器---->jvm可执行的Java字…