zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Spring源码学习笔记8——Spring是如何解决循环依赖的

2023-04-18 12:33:02 时间

一丶什么是循环依赖

如图,如果用线条来表示bean之间的依赖关系,循环依赖会形成一个有向图,成环

二丶循环依赖解决的原理是什么

如果这个对象A还没创建成功,在创建的过程中要依赖另一个对象B,而另一个对象B也是在创建中要依赖对象A,这种肯定是无解的,这时我们就要转换思路,我们先把A创建出来,但是还没有完成初始化操作,也就是这是一个半成品的对象,然后在赋值的时候先把A暴露出来,然后创建B,让B创建完成后找到暴露的A完成整体的实例化,这时再把B交给A完成A的后续操作

实际上是bean1持有了bean2的引用

三丶spring是如何解决循环依赖的

如果是我们要解决这个问题,可以使用两个map,一个存放单例的对象叫sMap,一个存实例化但是没有完成属性注入的对象——半成品对象叫eMap,假设是A和B相互依赖的情况下,实例化A后直接把bean放入到eMap,然后发现A需要注入B,就去实例化B,把B放在eMap,发现B需要A,先去sMap中发现没有,再去eMap中发现存在,这个时候可以把A赋值给B对应的属性,B就完成属性注入了,把B从eMap中移除放入sMap,然后B注入给A对应的属性,再把A从eMap移除放入sMap,整个过程就结束了,但是spring还需要支持强大的AOP功能,所以过程更为复杂

1.spring为解决循环依赖设计的三个缓存

  • DefaultSingletonBeanRegistry 中使用了三级缓存来解决循环依赖

  • singletonObjects 单例池,保存所有创建完毕的单例对象,存成品对象,也叫一级缓存(key : bean的名称,value :bean对象)

  • earlySingletonObjects 存提前暴露的对象(半成品对象)也叫二级缓存

  • singletonFactories 三级缓存,key是bean名称,value是ObjectFactory对象

  • ObjectFactory

一个函数式接口,调用getObject方法可以获得bean

2.图解spring循环依赖解决流程

这里最难理解的在于,为什么还要一个三级缓存,三级缓存ObjectFactory有啥用

3.从源码来看如何解决循环依赖

《Spring源码学习笔记6——Spring bean的实例化》中我们知道,AbstractApplicationContext的finishBeanFactoryInitialization方法调用preInstantiateSingletons会进行全部单例,非懒加载bean,非抽象的bean实例化和初始化

getBean方法调用了doGetBean

3.1doGetBean

3.1.1第一次调用getSingleton——getSingleton(bean名称)

这里其实就是直接去一级缓存单例池中拿当前beanName对应的bean实例,发现没有且当前beanName并没有正在创建bean,直接返回null

3.2一些和三级缓存关系不大的操作

spring发现没有会先看父BeanFactory是否包含当前beanName的BeanDefinition,还会家住在当前beanName依赖的bean(@DependsOn注解,后续发现当前bean需要注入Bean1,还会记录当前bean 依赖于Bean1)

3.3第二次调用getSingleton——getSingleton(bean名称,() -> createBean)

这里的createBean就是前面笔记中提到的实例化并且初始化bean,可以看到getSingleton(bean名称,() -> createBean)的作用是如果bean在单例池中没有,那么创建一个并且初始化,最后放入到一级缓存中,后续会调用doCreateBean方法,先是createBeanInstance反射调用构造方法or CGLIB构造当前bean的子类实例化

重点看下

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

这个方法会将当前bean放入到三级缓存中,其中key是bean名称,value是

()->getEarlyBeanReference,getEarlyBeanReference有什么用,后面用到的时候我们再看

3.4 populateBean 循环依赖发生的地方

假设是上面这种情况,spring当前正在创建A,发现需要注入B,这里我们以@Autowried依赖注入为例,会调用到 AutowiredAnnotationBeanPostProcessor#postProcessProperties方法进行一来注入,然后调用beanFactory#resolveDependency方法去获取需要的bean,接着实际是doResolveDependency方法去找到合适的bean,最终是调用 beanFactory.getBean(beanName),这里的beanName就是b,spring会创建B(流程和创建A一样),在populateBean为B对象赋值的时候,发现需要A,会调用beanFactory.getBean(a),这个时候会先从缓存中拿,也就是下面的

3.5第三次调用getSingleton—getSingleton(bean名称)

这里最终会在三级缓存中找到A对应的ObjectFactory,并且调用getObject,实际上是调用getEarlyBeanReference方法

循环调用所有SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference,作用就是如果当前bean需要进行AOP的加强,这个方法可以对应bean进行动态代理,调用完在这个方法之后,会把A放入到二级缓存,然后B就获得了A的引用进行了依赖注入,至此B就完成了依赖注入,可以调用其他初始化的方法。然后B就初始化结束被放入到一级缓存,对于A来说B被返回,A可以将B注入到属性上

3.6第四次调用getSingleton—getSingleton(bean名称,不允许早期循环引用)

上面我们说到SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference方法可以为A生成动态代理记作AProxy,B进行属性注入的对象也是AProxy

但是A执行完populateBean和initializeBean后,还是一个普通的对象A而不是被动态代理的AProxy,AProxy此时存储在二级缓存,那么spring需要对A进行替换,不如最终是A被放入单例池,A将失去AOP(比如事务)的加强。

这里会拿到AProxy替换掉A,也就是说最后保存在一级缓存的对象是AProxy并不是A

那么这里为什么要指定allowEarlyReference为false昵,这个false表示不需要再使用后置处理器处理A了,A已经被代理然后存在二级缓存,如果再次代理依次,岂不是单例的对象在容器中存在两个

至此A将被Aproxy代替,被放入到一级缓存,从二三级中移除

你可能好奇,上面执行initializeBean的是A对象,后面被AProxy替换,那么AProxy岂不是没有执行初始化
不是这样的AProxy实质上内部持有了一个A,A初始化了对于AProxy来说就执行了初始化,因为处理AOP的加强逻辑之外,其余的方法调用实际上都是A对象在发光发热

四丶为什么需要二级缓存

  • 二级缓存也被称作半成品池,为什么是半成品,因为被放入进行的对象还没有完成完成Bean的属性注入和初始化方法调用,

  • 那么要二级缓存干嘛

    如果我们存在如下的依赖关系

A需要B,这个时候spring先去创建B,B需要A,会从三级缓存拿到B的ObjectFactory得到代理的A,并且A被放入二级缓存,返回B继续注入C,spring去创建C,发现C依赖于A先从缓存拿,一级没有,二级存在AProxy,如果假设不存在二级缓存那么此时C还会从三级缓存拿到A的ObjectFactory生成一个AProxy1,这样spring容器中的单例池不知道到底保存哪一个好,B的属性A和C的属性A却不是同一个A

这就是二级缓存的作用,避免多次对A进行AOP增强产品多个A的代理对象,并且可以说加快了一点效率,如果每次都循环所有SmartInstantiationAwareBeanPostProcessor调用getEarlyBeanReference岂不是很浪费时间

五丶为什么需要三级缓存

确实解决循环依赖,二级缓存就够了,但是要对对象进行AOP的增强就需要用到三级缓存

三级缓存是用于存放原始的注入对象的ObjectFactory,这些对象还是简单对象并没有被代理,三级缓存是用于解决循环依赖问题,当对象A依赖对象B时,A对象就会被先临时存放在三级缓存中,然后初始化B对象。在这个时间点如果有别的注入对象需要依赖A对象就会从三级缓存中查询,并通过三级缓存的getObject方法生成代理对象然后将A对象的ObjectFactory从三级缓存中删除,把被AProxy放入二级缓存