手把手教你调试SpringBoot启动 IoC容器初始化源码,spring如何解决循环依赖
- 授人以鱼不如授人以渔,首先声明这篇文章并没有过多的总结和结论,主要内容是教大家如何一步一步自己手动debug调试源码,然后总结spring如何解决的循环依赖,最后,操作很简单,有手就行。
- 本次调试 是使用@Autowired注入,通过来调试源码看spring如何解决的循环依赖问题。
- 首先创建一个简单的springBoot项目,引入spring-boot-test包即可。可以使用idea提供的spring官网推荐的快速创建。
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency><dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
</dependency><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
<scope>compile</scope>
</dependency>编写调试源码的三个类
@Getter
@Setter
@Component
public class A {
@Autowired
private B b;
}@Getter
@Setter
@Component
public class B {
@Autowired
private A a;
}@SpringBootTest
@RunWith(SpringRunner.class)
public class IocCodeSourceTest {
@Autowired
private ApplicationContext applicationContext;
@Test
public void test_sourceCode_01() {
Object bean = applicationContext.getBean("a");
Object bean1 = applicationContext.getBean("b");
//通过在此打断点设置可以发现,A和B都初始化完成了
System.out.println(bean);
System.out.println(bean1);
}
}- 注意:这里不能使用lombok提供的注解@Data,因为@Data中重写了equals和hascodes方法,
System.out.println(bean)
这一句代码这输出的时候,会去调用bean的hashcode,在计算A对象的hashcode的时候,又调用了属性B的hashcode方法,同理计算B属性的hashcode由去调用了A的,造成了死递归。最终造成线程的虚拟机栈(方法栈)一直入栈,栈溢出。如下图,hashcode9331和9332一直会递归调用。
- 没时间的童鞋可以直接去看我的github项目地址:https://github.com/coffeebabeCGG/spring/tree/master/src/main/java/com/example/springstudy
- 调试方法:
首先需要知道,spring默认创建bean都是单例的,而单例的bean是在容器初始化完成后就创建完成。因此这里使用springboot启动调试,最终还是会进入到run方法,到refresh()方法,再去初始化容器。
下面会教几个关键的debug调试点。掌握这几个关键的调试点后,源码怎么走的,可以看最终的调试结果流程图,自己结合源码去一步一步debug。
1、进入getbean()
2、紧接着进去AbstractBeanFactory实现方法
3、最后进入AbstractBeanFactory的doGetBean()
4、关键点:在debug上设置条件,因为容器初始化的时候,会有很多spring本身的bean需要先去加载,所以,要在这里设置我们需要去debug观察的bean,我们的bean没有配置beanName,默认使用的类名小写。
5、接下来就可以一路F9放行,小伙伴们看自己的快捷键,每个人都不一样。当断点停下,就可以观察spring在创建 A B对象的调用栈了。
关键点:调试源码的时候,深入之后很容易就迷路,有个小技巧。下面截图的左下角红色标注,小伙伴们一定记得学会看方法的调用栈。为了简单理解,可以先这么理解,在开始调用一个方法时候,Jvm会创建一个线程,例如下图就是main线程。
然后 方法之间的调用是通过栈 这个数据结构来实现的。Jvm中称为java虚拟机栈,每一个方法都是一个栈帧。
结合下图去理解:
main线程中(即一个虚拟机栈):run方法是一个栈帧,然后run方法中调用了refreshContxt,那么refreshContext也是一个栈帧,被压入栈顶,以此类推。目前我们当前main线程的虚拟机栈的栈顶是doGetBean方法。
说了这么多有什么用呢?加入我们在调试过程中,迷路了,我们可以看下调用栈,找到我们来的地方。是哪一个栈帧,可以利用idea提供的下图的回到上一个断点。再回到我们开始的地方,这样一步一步调试就不会迷路了。
- 调试流程图:
- 最后附上自己调试之后的总结流程图。下一篇博客,会结合这个流程图,解释一下,三级缓存的具体作用,以及为什么需要使用到了三级缓存?
手把手教你调试SpringBoot启动 IoC容器初始化源码,spring如何解决循环依赖的更多相关文章
- SpringBoot框架——从SpringBoot看IoC容器初始化流程之方法分析
目录 一.概观Spring Boot 二.Spring Boot应用初始化 2.1 初始化入口 2.2 SpringApplication的run方法 2.3 方法分析 三.容器创建与初始化 3.1 ...
- 手把手教你撸一套Redux(Redux源码解读)
Redux 版本:3.7.2 Redux 是 JavaScript 状态容器,提供可预测化的状态管理. 说白了Redux就是一个数据存储工具,所以数据基础模型有get方法,set方法以及数据改变后通知 ...
- 手把手教你实现栈以及C#中Stack源码分析
定义 栈又名堆栈,是一种操作受限的线性表,仅能在表尾进行插入和删除操作. 它的特点是先进后出,就好比我们往桶里面放盘子,放的时候都是从下往上一个一个放(入栈),取的时候只能从上往下一个一个取(出栈), ...
- SpringBoot事件监听机制源码分析(上) SpringBoot源码(九)
SpringBoot中文注释项目Github地址: https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE 本篇接 SpringApplicat ...
- 【Spring】Spring IOC原理及源码解析之scope=request、session
一.容器 1. 容器 抛出一个议点:BeanFactory是IOC容器,而ApplicationContex则是Spring容器. 什么是容器?Collection和Container这两个单词都有存 ...
- 手把手教你调试Linux C++ 代码(一步到位包含静态库和动态库调试)
手把手教你调试Linux C++ 代码 软件调试本身就是一项相对复杂的活动,他不仅要求调试者有着清晰的思路,而且对调试者本身的技能也有很高的要求.Windows下Visual Studio为我们做了很 ...
- 【Spring源码解析】—— 结合SpringMVC过程理解IOC容器初始化
关于IOC容器的初始化,结合之前SpringMVC的demo,对其过程进行一个相对详细的梳理,主要分为几个部分: 一.IOC的初始化过程,结合代码和debug过程重点说明 1. 为什么要debug? ...
- 挖坟之Spring.NET IOC容器初始化
因查找ht项目中一个久未解决spring内部异常,翻了一段时间源码.以此文总结springIOC,容器初始化过程. 语言背景是C#.网上有一些基于java的spring源码分析文档,大而乱,乱而不全, ...
- 【spring源码分析】IOC容器初始化(一)
前言:spring主要就是对bean进行管理,因此IOC容器的初始化过程非常重要,搞清楚其原理不管在实际生产或面试过程中都十分的有用.在[spring源码分析]准备工作中已经搭建好spring的环境, ...
随机推荐
- xv6学习笔记(3):中断处理和系统调用
xv6学习笔记(3):中断处理和系统调用 1. tvinit函数 这个函数位于main函数内 表明了就是设置idt表 void tvinit(void) { int i; for(i = 0; i & ...
- WPF下获取文件运行路径、运行文件名等
在客户端开发过程中,经常需要获取相对路径的一些资源,而相对路径的就与客户端运行文件的路径息息相关了.在以前的winform开发中,我们可以使用 System.Windows.Forms.Applica ...
- Java社区——个人项目开发笔记(二)
1.B\S架构通信原理 浏览器,服务器之间产生通信,浏览器访问服务器,服务器返回一个HTML,浏览器会对HTML进行解析,并渲染相关的内容. 在解析过程中,会发现HTML里引用了css文件,js文件, ...
- snoop的基本用法
关于Snoop的用法 snoop是开发wpf应用程序的利器.用它可以观察WPF的可视树,监听事件,更改元素属性等. 下面我介绍下snoop一些用法. 1.获取指定应用程序的UI 打 ...
- WPF 自己做一个颜色选择器
程序开发过程中,经常会遇到需要支持动态配置主题颜色的问题,通常,一个程序会有多种不同的颜色风格主题供选 有时候,更细致一些的地方,会需要支持自己配置颜色,这样我们就需要一个颜色选择器啦,下面是我自己开 ...
- C#串口通信SeriPort 电表DLT645 RS234/RS485
难受,三个多月前有一个电表电量监控的项目.做完了就没再管了.今天有需求需要改一些地方,但是....我想不起来干了啥,怎么干的啦.真的完全忘了.....项目名称叫啥都忘了.找了半天 不知道有没有和我一样 ...
- WPF---数据绑定(一)
一.何为数据绑定 场景:考虑一个Window上有一个TextBox和一个Slider两个元素,当我们拖动滑动条的时候,会在TextBox中显示当前滑动条的数值:当我们在TextBox中输入一个有效值, ...
- uwp 的work project 的 取消闹钟
private void initalAlarmHanle() { string cancelAlarm = "CancelAlarmEvent"; ConnectionManag ...
- 如何让BootStrap栅格之间留出空白间隙呢?
BootStrap栅格之间留出空隙 BootStrap栅格系统可以把我们的container容器划分为若干等分,如果想要每个部分之间留出一定的空隙,我们很可能首先想到的方法就是用margin外边距来使 ...
- springboot中redis取缓存类型转换异常
异常如下: [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested ...