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

有目标就不怕路远。年轻人.无论你现在身在何方.重要的是你将要向何处去。只有明确的目标才能助你成功。没有目标的航船.任何方向的风对他来说都是逆风。因此,再遥远的旅程,只要有目标.就不怕路远。没有目标,哪来的劲头?一车尔尼雷夫斯基

导读:本篇文章讲解 【保姆级】手把手Debug循环依赖的整体流程,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

【Spring源码系列- IOC】

1

【Spring源码】0.安装Gradle环境

2

【Spring源码】1.下载与编译_pom relocation to an other version number is not f

3

【Spring源码】2.试个水先~Debug找到传说中的三级缓存(图解向,堆图预警)

4

【Spring源码】3. xml文件如何转换成BeanDefinition(主要涉及prepareRefresh()+ obtainFreshBeanFactory()两个函数,图解向,堆图预警)_spring xml转bean

5

【Spring源码】4. 自己搞个标签?~自定义标签保姆级全过程(图解向,堆图预警)

6

【Spring源码】5.spring的bean工厂准备工作(prepareBeanFactory(beanFactory)

7

【Spring源码】6. Spring扩展自定义属性编辑器保姆级教程

8

【Spring源码】7. 如何添加自定义的BeanFactoryPostProcessor

9

【Spring源码】8. 捋下invokeBeanFactoryPostProcessors()主要处理流程

10

【Spring源码】9. 超级重要的ConfigurationClassPostProcessor

11

【Spring源码】10. 递归调用的processConfigurationClass()方法

12

【Spring源码】11. 我是注解类不?checkConfigurationClassCandidate()注解类判断方法详解

13

【Spring源码】12. 注册bean处理器registerBeanPostProcessors()

14

【Spring源码】13. 国际化处理initMessageSource()源码解析

【补充内容】【保姆级】SpringBoot项目中的i18n国际化

15

【Spring源码】14. 消息多播器(观察者模式)

【补充内容】【保姆级示例向】观察者模式

16

【Spring源码】15. Bean的创建过程(1.概述篇)

17

【Spring源码】16. Bean的创建过程(2)

18

【Spring源码】17.创建Bean这篇认真的@(・●・)@

【补充内容】

【保姆级·创建对象】如何通过Supplier创建对象

【保姆级·创建对象】如何通过factory-method创建对象

【保姆级·创建对象】如何利用resolveBeforeInstantiation()在预处理阶段返回一个Bean的实例对象

19

【Spring源码】18. factory-method创建对象关键函数详解:instantiateUsingFactoryMethod()

20

【Spring源码】19. 没合适的构造器?找determineCandidateConstructors()!

21

【Spring源码】20. MergedBeanDefinitionPostProcessor修改/合并bean定义

【补充内容】

【保姆级】@PostConstruct & @PreDestroy使用示例

【Spring源码】AutowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition()详解

【Spring源码】CommonAnnotationBeanPostProcessor.postProcessMergedBeanDefinition()详解

22

【Spring源码】21. 初探循环依赖

【补充内容】

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

【实践向】当移除了三级缓存……

【分析向】没有三级缓存会导致什么?

【Spring源码】插播一个创建代理对象的wrapIfNecessary()方法

23

【Spring源码】22. 属性填充populateBean()详解

【补充内容】

【Spring源码】自动注入·名称:autowireByName()详解

【Spring源码】自动注入·类型:autowireByType()详解

【Spring源码】属性值的解析与赋值:populateBean().applyPropertyValues()

【保姆级】超超超简单的自定义注解实现@Autowired同款功能

24

【Spring源码】23. 执行初始化逻辑:initializeBean()

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

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

或者这样看、

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

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

想瞅瞅三级缓存的庐山真面目可以移步到【Spring源码】2.试个水先~Debug找到传说中的三级缓存

想了解更多关于三级缓存的相关内容欢迎移步到【Spring源码】21. 关于循环依赖的N个问题

测试准备

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

BeanA.java

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

public class BeanA {BeanB beanB;

   public BeanB getBeanB() {return beanB;
   }public void setBeanB(BeanB beanB) {this.beanB = beanB;
   }}

BeanB.java

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

public class BeanB {BeanA beanA;

   public BeanA getBeanA() {return beanA;
   }public void setBeanA(BeanA beanA) {this.beanA = beanA;
   }}

circulate.xml

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

<?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

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

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

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

AbstractApplicationContext.java

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

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

AbstractBeanFactory.java

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

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

AbstractAutowireCapableBeanFactory.java

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

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

正式开撕

启动

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

点击Resume🔘,来到addSingletonFactory()

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

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

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

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

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

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

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

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

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

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

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

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

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

随后添加进三级缓存

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

回到启动类,获取到完整的BeanA和BeanB

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/135372.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!