spring循环依赖通过三级缓存解决过程分析-其二

前面大致分析了下bean的创建过程,下面通过源码分析,到底是如何解决循环依赖,以及为什么要这么做,或者不这么做行不行。

1. 分析准备

大致代码就是创建了三个Bean:Aservice、Bservice、Cservcie,还有一个Aspect
参数spring.main.allow-circular-references已经设置成true了。

a依赖了b和c,通过aop对a的方法织入了环绕通知。
b依赖了a
c不依赖

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.源码分析

这里假设先创建a,此时b和c都是null。开始进入a的创建过程。
在这里插入图片描述
在这里插入图片描述
执行addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
在这里插入图片描述
检查是否需要生成代理
在这里插入图片描述
执行依赖注入和初始化
在这里插入图片描述
检查当前bean是否已经是代理对象了,如果不是,则把当前bean的引用指向二级缓存中的(如果有的话,没有就跳过)。
在这里插入图片描述
把单例放入一级缓存,同时删除二级、三级缓存。
在这里插入图片描述

3.详细过程分析

创建A->a的半成品->加入三级缓存(k:a, v: a的半成品的lamda)
->依赖注入b->
【 —递归调用
创建B->b的半成品->加入三级缓存(k:b,value:lamda)->依赖注入a->
先从一级、二级中找a,找不到,三级缓存中有,执行ObjectFactory.getObject(),这里面就会执行lambda表达式中的getEarlyBeanReference(beanName, mbd, bean)方法,这个方法里面会生成包装了a半成品的代理对象,然后把a半成品的代理对象放入二级缓存,把a的半成品移除三级缓存-》b注入a的代理对象完成->b执行初始化->执行检查逻辑(if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);}
这个方法只会检查一二级缓存,由于b有个lambdal存在三级缓存,就不会执行if后面的,直接返回b的成品对象
)->b单例创建完成,存入一级缓存,同时移除二、三级缓存(其实只有三级缓存中有b)

接着继续a的依赖注入:(这个时候二级缓存中有个a的半成品的代理对象,后续还是针对之前a的半成品进行后续操作,这个半成品就是当前方法里面的一个局部变量,因为之前执行依赖注入被打断了,现在继续执行后续方法)
->a半成品依赖注入完成->a半成品执行初始化->b执行检查逻辑(由于a有个代理对象在二级缓存,然后会把a的代理对象,赋值给当前正在创建的a对象引用,exposedObject = earlySingletonReference;由于a内部包装了当前的a对象)->a的代理对象存在一级缓存,同时移除二三级缓存(其实只有二级缓存有个a的代理对象)。

这个时候spring容器完成初始化,这里面包含了a的代理对象和b的对象。
没有出现循环依赖的时候,代理对象在initializeBean方法中的applyBeanPostProcessorsAfterInitialization
调用BeanPostProcessor的postProcessAfterInitialization

这里a对象需要代理,但是由于循环依赖导致a的代理创建时机提前了,在b注入a的时候就创建了a的代理对象放入了二级缓存。如果不提前后面调用BeanPostProcessor,当执行AbstractAutoProxyCreator的postProcessAfterInitialization方法会检查
if (this.earlyProxyReferences.remove(cacheKey) != bean)
这个earlyProxyReferences里面存了a对象的引用,在调用
getEarlyBeanReference-》getEarlyBeanReference,会把a的代理对象放入二级缓存,同时把a对象放入到了earlyProxyReferences,这样就不会重复进行代理了!!!

4.为什么要三级缓存

现在a和b产生了循环依赖,

4.1

假设只有一级缓存,那么只有成品bean才会进入到里面,那么a和b都没法完成注入,因为互相都找不到对方的对象,a和b都只是方法运行时栈里面的一个局部变量,互相是没办法看到的。

4.2

假设直接加入二级缓存,主要存储半成品bean(或者半成品bean的代理对象),a产生实例以后放入二级缓存,a要依赖注入不存在的b,

执行b的创建,b放入二级缓存,b执行依赖注入二级缓存的a成功,b初始化,b放入一级缓存,移除二级缓存的b

a注入一级缓存的b成功,a初始化,a加入一级缓存,移除a的二级缓存。

二级缓存是不是可以完美解决循环依赖了,看着一点问题都没有,肯定可以完美解决了。

其实没有aop的话,二级缓存就可以解决循环依赖了,但是加入aop以后,就会有点小问题。如果就是要用二级缓存,也是可以解决的,但是没必要这么做。
什么问题呢?
a配了aop了,b依赖注入a的时候,实际上要注入a的代理,这个时候a就要提前创建代理。关键b依赖注入a是在a依赖注入b的时候,,发现b不存在,创建b的过程中,才知道b循环依赖了a自己。
解决办法就是每个bean创建实例以后,都先提前检查是否需要创建代理,然后把代理对象放入二级缓存中。而spring后续的BeanPostProcessor肯定还会检查bean是否需要生成代理,这就相当于代理逻辑执行了两遍。
这样是不是很麻烦,因为项目中是不是大部分的bean都不会循环依赖,循环依赖的只是少数,为了解决少数bean循环依赖,就让全部的bean都先检查是不是需要创建代理,需要就创建代理放入二级缓存,不需要就把原对象放入二级缓存。其实大部分的bean都是可以直接进入一级缓存的,加上一个多余的加入二级缓存的步骤

4.3

spring就想了个办法,加入三级缓存,先把代理逻辑使用lambda缓存起来不执行,如果真的发生了循环依赖就执行,这样保证绝大多数的bean的代理都是在BeanPostProcessor生成。针对二级缓存的问题,设计了三级缓存,就是bean首先都加入三级缓存,这个时候不会创建代理,这就是lambda的好处,可以在需要的时候才会执行lambda里面的逻辑。

4.3.1

当a依赖b,

b创建对象,存入一个lambda到三级缓存,b又依赖a的时候,b会从三级缓存取出lambda执行,然后把a的代理放入二级缓存中,删除a在三级缓存的lambda,b执行后续,然后把b加入一级缓存,删除三级缓存的b,b并不需要lambda的逻辑。

a注入b成功,a执行后续,然后把二级缓存的a代理加入到一级缓存,删除二级缓存的a代理。
这个时候其实进入到二级缓存的都就是被循环依赖的bean,如果没有被循环依赖的bean是不会进入二级缓存的。

4.3.2

a注入c,c创建实例,c创建lambda加入三级缓存,然后c完成创建,c加入一级缓存,删除三级缓存c的lambda,a注入c成功。

5.总结

使用三级缓存,前提就是spring觉得大部分程序,都应该是没有循环依赖,或者说循环依赖很少。基于这个理念,理想情况就是大部分的bean都是直接完成整个生命周期,在初始化中检查是否创建代理对象,然后把bean存入一级缓存,为了完成这个理想,加入了二级、三级缓存,尤其是三级缓存,延迟bean的代理创建时机,只有当发生循环依赖的时候不得已提前创建bean的代理放入二级缓存,最好的情况就是二级缓存永远都是空的,创建一个bean加入一级缓存然后就移除对应三级缓存中的lambda。这里面唯一的多余步骤就是,加入了二级缓存的bean,也就是被循环依赖的bean(或者bean代理),在后面bean初始化中还要检查一遍是否需要创建代理,因为被循环依赖的bean很少,所以基本这个多余步骤可以忽略,但是如果只用一级、二级的话,多余步骤就是对每个bean都是多余的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值