0


【Spring从成神到升仙系列 五】从根上剖析 Spring 循环依赖

  • 👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主
  • 📕系列专栏:Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙、Spring从成神到升仙系列
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
  • 📝联系方式:hls1793929520,加我进群,大家一起学习,一起进步👀

在这里插入图片描述

文章目录

Spring 循环依赖源码解析

一、引言

对于Java开发者而言,关于

Spring

,我们一般当做黑盒来进行使用,不需要去打开这个黑盒。

但随着目前程序员行业的发展,我们有必要打开这个黑盒,去探索其中的奥妙。

本期

Spring

源码解析系列文章,将带你领略

Spring

源码的奥秘

本期源码文章吸收了之前

Kafka

源码文章的错误,将不再一行一行的带大家分析源码,我们将一些不重要的部分当做黑盒处理,以便我们更快、更有效的阅读源码。

废话不多说,发车!
在这里插入图片描述

本文流程图可关注公众号:爱敲代码的小黄,回复:循环依赖 获取
贴心的小黄为大家准备的文件格式为 POS文件,方便大家直接导入 ProcessOn 修改使用

二、循环依赖场景

我们上几篇文章讲解了 IOC、AOP的源码实现,如果没有看过的同学可以去看一下:

  • Spring IOC 源码剖析
  • Spring AOP 源码剖析

如果上面的文章你已经熟悉了,那么对于循环依赖的理解就会变得很简单,甚至你自己都能够想明白整个运行原理

我们首先介绍一下循环依赖的场景

我们在委托

Spring

进行对象的创建时,会遇到下面的情况:

1、有参构造引起的循环依赖

MyDemo1:

publicclassMyDemo1{publicMyDemo2 myDemo2;publicMyDemo1(MyDemo2 myDemo2){this.myDemo2 = myDemo2;}}

MyDemo2:

publicclassMyDemo2{publicMyDemo1 myDemo1;publicMyDemo2(MyDemo1 myDemo1){this.myDemo1 = myDemo1;}}

xml文件配置:

<?xml version="1.0" encoding="UTF-8"?><beansxmlns="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.xsd"><beanid="myDemo1"class="cn.hls.demo1.MyDemo1"><constructor-argvalue="myDemo2"/></bean><beanid="myDemo2"class="cn.hls.demo1.MyDemo2"><constructor-argvalue="myDemo1"/></bean></beans>

测试用例:

publicclassTestMain{publicstaticvoidmain(String[] args){ApplicationContext context =newGenericXmlApplicationContext("application.xml");MyDemo1 myDemo1 =(MyDemo1) context.getBean("myDemo1");
        myDemo1.show();}}

运行,不出所料,我们会报错:

Caused by:org.springframework.beans.factory.BeanCurrentlyInCreationException:Error creating bean withname 'myDemo1':Requested bean is currently in creation:Is there an unresolvable circular reference?

2、属性注入引起的循环依赖

MyDemo1:

publicclassMyDemo1{publicMyDemo2 myDemo2;publicvoidshow(){System.out.println("我是"+MyDemo1.class.getName());}publicvoidsetMyDemo2(MyDemo2 myDemo2){this.myDemo2 = myDemo2;}publicMyDemo2getMyDemo2(){return myDemo2;}}

MyDemo2:

publicclassMyDemo2{publicMyDemo1 myDemo1;publicvoidshow(){System.out.println("我是"+MyDemo2.class.getName());}publicMyDemo1getMyDemo1(){return myDemo1;}publicvoidsetMyDemo1(MyDemo1 myDemo1){this.myDemo1 = myDemo1;}}

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.xsd"><bean id="myDemo1"class="cn.hls.demo1.MyDemo1"><property name="myDemo2" ref="myDemo2"/></bean><bean id="myDemo2"class="cn.hls.demo1.MyDemo2"><property name="myDemo1" ref="myDemo1"/></bean></beans>

测试用例:

publicclassTestMain{publicstaticvoidmain(String[] args){ApplicationContext context =newGenericXmlApplicationContext("application.xml");MyDemo1 myDemo1 =(MyDemo1) context.getBean("myDemo1");MyDemo2 myDemo2 =(MyDemo2) context.getBean("myDemo2");

        myDemo1.show();
        myDemo2.show();}}

运行,我们竟然发现,这种是可以正常执行的

我是cn.hls.demo1.MyDemo1
我是cn.hls.demo1.MyDemo2

到这里,有没有一点点惊讶、一点点懵逼、一点点卧槽

如果有的话,那这篇文章将带你解析为什么两种方式不同的注入方式

一种可能正常运行,一种不能正常运行

三、循环依赖的原因

这里我们搬出

IOC

源码中的流程图:
在这里插入图片描述

我们分别聊一下有参构造场景下和有参注入场景下的不同

1、有参构造失败的原因

我们通过上图看到,如果一个类需要通过有参构造创建实例化,那么需要得到其构造方法的入参:

在这里插入图片描述

整体情况如上所示,我们总是重复性的循环,

MyDemo1

的实例化创建依赖

MyDemo2

,而

MyDemo2

的实例化创建又需要依赖

MyDemo1

,这样就导致了死循环并无法解决。

所以,当我们的

Spring

察觉到有参构造导致的循环依赖时,会进行报错,这种的循环依赖也是没有办法解决的。

2、属性注入成功的原因

在这里插入图片描述

大家看这张图,可能会疑惑,这不也造成了循环依赖嘛,怎么这种方式没报错

我们想想这种属性注入导致的循环依赖能不能靠其他的方式去解决,加缓存可不可以

在这里插入图片描述
我们来看这种解决方式:

  • 我们 MyDemo1 调用无参构造生成实例(不是完全的实例)时,将其放至我们的缓存池中
  • MyDemo1 调用属性注入时,会去缓存池中寻找 MyDemo2 的实例,若找不到的话,则调用 CreateBean 方法创建 MyDemo2 的实例
  • MyDemo2 调用无参构造生成实例(不是完全的实例)时,将其放至我们的缓存池中
  • MyDemo2 调用属性注入时,会去缓存池中寻找 MyDemo1 的实例,找到之后之前,执行后续的方法生成对应的实例化
  • 这个时候我们的 MyDemo1 已经得到了 MyDemo2 的实例化数据了,直接执行初始方法创建实例即可

通过上述这种方式,我们已经将

属性注入

的循环依赖问题用加一层缓存的方式解决掉了

而这个缓存也被我们称作 提前暴露(earlySingletonObjects) 的缓存

2.1 AOP导致的循环依赖

我们上面可以看到,我们用一层 提前暴露(earlySingletonObjects) 的缓存解决了属性注入导致的循环依赖问题

这时候你可能会说:小黄,小黄,不是三级缓存嘛,你这咋就讲了一个 提前暴露(earlySingletonObjects) 缓存

不要着急,我们继续往下讲

假如我们现在

MyDemo1

AOP

动态代理,如果我们再按照上面的方式去进行缓存,会造成什么结果?

我们 MyDemo2 中的成员变量 MyDemo1 是未经动态代理的,这样使用 MyDemo1 时,实际上也是非动态代理的对象,这样是不被允许的!

为什么会有上面的问题呢?
在这里插入图片描述
根本原因在于:我们的属性注入的阶段在我们的执行初始方法(AOP)之前,缓存池中的半实例化对象不是我们代理对象

那怎么解决这个问题呢——没错,还是加缓存

我们再加一层缓存,该缓存的作用:如果我们半实例化的对象是代理对象,那么我们得到其代理对象

在这里插入图片描述

如上所示,整体的业务如上,我们详细的聊一聊

Spring

源码对于循环依赖的处理

四、循环依赖 Spring 源码剖析

我们以属性注入的例子来进行源码解析:

在我们讲解之前,我介绍一下三级缓存各自的功能:

  • 一级缓存(singletonObject):存储的是所有创建好了的单例Bean
  • 二级缓存(earlySingletonObjects):完成实例化,但是还未进行属性注入及初始化的对象
  • 三级缓存(singletonFactories):提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象

这三个缓存非常重要,必须要记住。

当我们使用

ApplicationContext context = new GenericXmlApplicationContext("application.xml");

启动时,会进行我们

Bean

的创建

这里只说最关键的步骤,整体的步骤可见:Spring IOC 源码剖析

整体流程如下:
在这里插入图片描述

步骤一:查询 MyDemo1 是否存在

此时的缓存:
在这里插入图片描述

我们直接跳到这里:

AbstractBeanFactory

246

protected<T>TdoGetBean(String name,@NullableClass<T> requiredType,@NullableObject[] args,boolean typeCheckOnly){// Step1:查询MyDemo1缓存是否存在Object sharedInstance =getSingleton(beanName);// 如果是单例的beanif(mbd.isSingleton()){// 直接创建bean即可,注意 getSingleton 方法
        sharedInstance =getSingleton(beanName,()->{returncreateBean(beanName, mbd, args);});
        bean =getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}}// Step1:从三级缓存中查询 MyDemo1 是否被缓存protectedObjectgetSingleton(String beanName,boolean allowEarlyReference){// 一级缓存查询Object singletonObject =this.singletonObjects.get(beanName);if(singletonObject ==null&&isSingletonCurrentlyInCreation(beanName)){// 二级缓存查询
            singletonObject =this.earlySingletonObjects.get(beanName);if(singletonObject ==null&& allowEarlyReference){synchronized(this.singletonObjects){
                    singletonObject =this.singletonObjects.get(beanName);if(singletonObject ==null){
                        singletonObject =this.earlySingletonObjects.get(beanName);if(singletonObject ==null){// 三级缓存查询ObjectFactory<?> singletonFactory =this.singletonFactories.get(beanName);if(singletonFactory !=null){
                                singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;}// 这里记住一个操作:在我们创建bean结束之后,会调用 addSingleton 该方法publicObjectgetSingleton(String beanName,ObjectFactory<?> singletonFactory){finally{if(recordSuppressedExceptions){this.suppressedExceptions =null;}afterSingletonCreation(beanName);}if(newSingleton){addSingleton(beanName, singletonObject);}return singletonObject;}

步骤二:将 MyDemo1 半实例化放至缓存中

我们直接跳到

AbstractAutowireCapableBeanFactory

580

protectedObjectdoCreateBean(String beanName,RootBeanDefinition mbd,@NullableObject[] args){// 是否需要提前暴露boolean earlySingletonExposure =(mbd.isSingleton()&&this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));// 如果需要提前暴露,则放入到我们的三级缓存里面if(earlySingletonExposure){addSingletonFactory(beanName,()->getEarlyBeanReference(beanName, mbd, bean));}}// 将未完全实例化的 MyDemo1 放至缓存中protectedvoidaddSingletonFactory(String beanName,ObjectFactory<?> singletonFactory){synchronized(this.singletonObjects){if(!this.singletonObjects.containsKey(beanName)){// 三级缓存this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);// 这个主要是记录当前注册的对象(不太重要)this.registeredSingletons.add(beanName);}}}// 这个是重点:生成动态代理对象的地方,我们后面会讲protectedObjectgetEarlyBeanReference(String beanName,RootBeanDefinition mbd,Object bean){Object exposedObject = bean;if(!mbd.isSynthetic()&&hasInstantiationAwareBeanPostProcessors()){for(BeanPostProcessor bp :getBeanPostProcessors()){if(bp instanceofSmartInstantiationAwareBeanPostProcessor){SmartInstantiationAwareBeanPostProcessor ibp =(SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;}

此时的缓存:
在这里插入图片描述

步骤三、四:查询 MyDemo2 的缓存是否存在

我们直接跳到这里:

AbstractBeanFactory

246

protected<T>TdoGetBean(String name,@NullableClass<T> requiredType,@NullableObject[] args,boolean typeCheckOnly){// Step4:查询MyDemo2缓存是否存在Object sharedInstance =getSingleton(beanName);// 如果是单例的beanif(mbd.isSingleton()){
        sharedInstance =getSingleton(beanName,()->{returncreateBean(beanName, mbd, args);});
        bean =getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}}

步骤五:将 MyDemo2 半实例化放至缓存中

我们直接跳到

AbstractAutowireCapableBeanFactory

580

protectedObjectdoCreateBean(String beanName,RootBeanDefinition mbd,@NullableObject[] args){// 是否需要提前暴露boolean earlySingletonExposure =(mbd.isSingleton()&&this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));// 如果需要提前暴露,则放入到我们的三级缓存里面if(earlySingletonExposure){addSingletonFactory(beanName,()->getEarlyBeanReference(beanName, mbd, bean));}}

此时的缓存:
在这里插入图片描述

步骤六:从缓存中获取 MyDemo1

我们直接跳到这里:

AbstractBeanFactory

246

protected<T>TdoGetBean(String name,@NullableClass<T> requiredType,@NullableObject[] args,boolean typeCheckOnly){// Step6:从缓存中获取 MyDemo1 Object sharedInstance =getSingleton(beanName);// 如果是单例的beanif(mbd.isSingleton()){
        sharedInstance =getSingleton(beanName,()->{returncreateBean(beanName, mbd, args);});
        bean =getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}}
protected<T>TdoGetBean(String name,@NullableClass<T> requiredType,@NullableObject[] args,boolean typeCheckOnly){// 这里获取的是 MyDemo1 的缓存,我们之前已经放入过Object sharedInstance =getSingleton(beanName);}protectedObjectgetSingleton(String beanName,boolean allowEarlyReference){Object singletonObject =this.singletonObjects.get(beanName);if(singletonObject ==null&&isSingletonCurrentlyInCreation(beanName)){
            singletonObject =this.earlySingletonObjects.get(beanName);if(singletonObject ==null&& allowEarlyReference){synchronized(this.singletonObjects){
                    singletonObject =this.singletonObjects.get(beanName);if(singletonObject ==null){
                        singletonObject =this.earlySingletonObjects.get(beanName);if(singletonObject ==null){// 【重点】从三级缓存中取到ObjectFactory<?> singletonFactory =this.singletonFactories.get(beanName);if(singletonFactory !=null){// 调用 getEarlyBeanReference 的方法生成对象
                                singletonObject = singletonFactory.getObject();// 将生成的半实例对象放至二级缓存中this.earlySingletonObjects.put(beanName, singletonObject);// 删除掉三级缓存的信息this.singletonFactories.remove(beanName);}}}}}}return singletonObject;}

我们来看一下

getEarlyBeanReference

做了什么、

  • 如果是普通的类,没有被动态代理的,直接返回 bean 即可
  • 如果是动态代理的类,需要进行动态代理类的生成并返回
protectedObjectgetEarlyBeanReference(String beanName,RootBeanDefinition mbd,Object bean){Object exposedObject = bean;if(!mbd.isSynthetic()&&hasInstantiationAwareBeanPostProcessors()){for(BeanPostProcessor bp :getBeanPostProcessors()){if(bp instanceofSmartInstantiationAwareBeanPostProcessor){SmartInstantiationAwareBeanPostProcessor ibp =(SmartInstantiationAwareBeanPostProcessor) bp;// 【重点】
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;}publicObjectgetEarlyBeanReference(Object bean,String beanName){Object cacheKey =getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);// 这里会生成动态代理类【AOP文章讲过】returnwrapIfNecessary(bean, beanName, cacheKey);}

到这里,我们的缓存的状态如下:
在这里插入图片描述

步骤七:将 MyDemo2 生成的实例化放至 singletonObject 中

publicObjectgetSingleton(String beanName,ObjectFactory<?> singletonFactory){if(newSingleton){addSingleton(beanName, singletonObject);}return singletonObject;}// 当bean初始化完成之后// 删除二级缓存、三级缓存,将其放入一级缓存中protectedvoidaddSingleton(String beanName,Object singletonObject){synchronized(this.singletonObjects){this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}

此时各缓存情况:
在这里插入图片描述

步骤八:将 MyDemo1 生成的实例化放至 singletonObject 中

publicObjectgetSingleton(String beanName,ObjectFactory<?> singletonFactory){if(newSingleton){addSingleton(beanName, singletonObject);}return singletonObject;}// 当bean初始化完成之后// 删除二级缓存、三级缓存,将其放入一级缓存中protectedvoidaddSingleton(String beanName,Object singletonObject){synchronized(this.singletonObjects){this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}

此时各缓存情况:
在这里插入图片描述
到这里,我们的循环依赖的整体流程就被解决了

五、总结

又是一篇大工程的文章结束了

记得校招时候,当时对

Spring

懵懂无知,转眼间也被迫看了源码

更可怕的是,现在面试竟然百分之80都要熟悉

IOC

AOP

的源码,甚至手写

AOP

的实现

但通过这篇文章,我相信,99% 的人应该都可以理解了

Spring 循环依赖

的实现

那么如何证明你真的理解了

Spring 循环依赖

呢,我这里出个经典的题目,大家可以想一下:为什么Spring要用三级缓存,二级不可以嘛?

如果你能看到这,那博主必须要给你一个大大的鼓励,谢谢你的支持!

喜欢的可以点个关注,

Spring

系列到此正式结束了~

  • 【Spring从成神到升仙系列 一】2023年再不会动态代理,就要被淘汰了
  • 【Spring从成神到升仙系列 二】2023年再不会 IOC 源码,就要被淘汰了
  • 【Spring从成神到升仙系列 三】2023年再不会 AOP 源码,就要被淘汰了
  • 【Spring从成神到升仙系列 四】从源码分析 Spring 事务的来龙去脉

后续博主应该会更新 dubbo 或者 并发编程 的系列文章,

我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,Java领域新星创作者,喜欢后端架构和中间件源码。

我们下期再见。

标签: java kafka 分布式

本文转载自: https://blog.csdn.net/qq_40915439/article/details/129700213
版权归原作者 爱敲代码的小黄 所有, 如有侵权,请联系我们删除。

“【Spring从成神到升仙系列 五】从根上剖析 Spring 循环依赖”的评论:

还没有评论