什么是循环依赖?
你拥有两个类、一个是classA、一个是classB、两个类之间相互引用就会造成循环依赖
public class ClassA{
@Autowired
ClassB classB;
}
public class ClassB{
@Autowired
ClassA classA;
}
根据Bean生命周期来说、第一步实例化Bean、第二步赋值、
1、实例化ClassA
2、给ClassA中的属性ClassB赋值、但是因为ClassB又有属性ClassA中、那么当ClassB实例化完、执行第二步给ClassB中的ClassA对象赋值之后、是否继续再实例化一个ClassA?如果按照这个理解、那不是无穷尽、无限初始化?事实真的如此?
接下来、看看Spring如何解决这个循环依赖问题:
在默认情况下、由上述ClassA跟ClassB的项目启动并不会报错、而是正常启动、这就是大名鼎鼎的三级缓存作用
先说循环依赖产生的场景问题(不说场景都是耍流氓):
场景一(Spring默认场景):
Bean作用域:单例(singleton)
注入方式: set注入
代码如最上面的ClassA跟ClassB、这种方式存在循环依赖的情况下Spring并不会报错、而是正常启动
场景二:
Bean作用域:单例(singleton)
注入方式: 构造注入
public class ClassA{
ClassB classB;
public ClassA(ClassB classB){
this.classB = classB;
}
}
启动报错
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'classB': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:265)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:325)
... 44 more
场景三:
Bean作用域:原型(prototype)
注入方式: set注入
启动报错、错误如上
那为什么场景一就没问题、而场景二、三就存在问题呢、下面具体分析一下Spring循环依赖解决原理
在场景一的情况下、之所以能够解决循环依赖的问题、是因为这种方式可以将实例化Bean和Bean属性赋值分开两个动作去完成、
实例化Bean的时候:调用无参构造方法完成、此时先不给属性赋值、而是将自己“曝光”给外界
Bean属性赋值的时候:调用set方法来赋值
两个步骤可以完全分开去完成、并且不要求同一时间点完成
实例化Bean的时候、曝光、就是实例化完成以后、先将自己放到缓存中、当所有Bean实例化完成以后、再一个个调用set方法给属性赋值、这样就解决了循环依赖问题、这个解决办法的实现、就是所谓的三级缓存
下面分析一下源码:
这三个缓存其实本质上是三个Map集合。
【一级缓存】singletonObjects
Cache of singleton objects: bean name to bean instance.
单例对象的缓存:key存储bean名称,value存储Bean对象
【二级缓存】earlySingletonObjects
Cache of early singleton objects: bean name to bean instance.
早期单例对象的缓存:key存储bean名称,value存储早期的Bean对象
【三级缓存】singletonFactories
Cache of singleton factories: bean name to ObjectFactory.
单例工厂缓存:key存储bean名称,value存储该Bean对应的ObjectFactory对象
我们再来看,在该类中有这样一个方法addSingletonFactory(),这个方法的作用是:将创建Bean对象的ObjectFactory对象提前曝光。
先从一级缓存中取、找不到再到二级缓存中取、找不到就到三级缓存获取之前曝光的ObjectFactory对象、通过ObjectFactory对象获取Bean实例
这就解决了场景一的情况下、循环依赖的问题、先把所有Bean都实例化、在ClassA实例化完给其中的属性ClassB赋值的时候、通过ClassB提前曝光出来的ObjectFactory对象拿到实例化的ClassB、同理、ClassB实例化过程中、因为ClassA已经曝光过了、所以也拿到ClassA的实例、这样就解决了循环依赖的问题
总结:
Spring只能解决setter方法注入的单例bean之间的循环依赖。ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题
学习动力节点老杜Spring6相关视频