所谓的循环依赖是指,A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。或者是 A 依赖 B,B 依赖 C,C 又依赖 A。它们之间的依赖关系如下:
spring容器最大的功能就是对bean的生命周期进行管理,每个bean在创建的过程中,需要得到一个完整的bean需要对bean的所有属性进行赋值,如果两个bean出现了相互依赖的情况,那么如果spring没有处理循环依赖,那么出现的结果就是在bean的创建过程中出现相互依赖,导致这个bean永远无法创建出来,则就导致一直在相互创建,最终出现的结果就是出现Stack Overflow异常,也就是栈溢出,类似于一个递归一样的。
spring在创建bean的时候,过程如下:
-
实例化前;
-
实例化bean(推断构造);
-
实例化后;
-
填充属性;
-
初始化前;
-
初始后;
上面的这些过程在第4步就会对bean进行属性填充,也就是依赖注入,依赖注入就会将bean中的所有属性进行填充,填充的时候如果发现了循环依赖,那么就会进行循环依赖的处理,如果spring没有处理循环依赖,那么出现的结果如下: 这样就会出现了一个闭环,程序用于无法结束,就会出现Stack Overflow异常。
spring是如何解决循环依赖
spring 的bean在创建bean的过程中会涉及到几个集合
-
singltonOjects:一级缓存(单例池)
-
earlySingltonObjects:二级缓存
-
singltonFactories:三级缓存
-
singletonsCurrentlyInCreation:存放正在创建bean的set集合,存放的是正在创建的bean的名字
一级缓存:一级缓存就是我们常常说的spring的单例容器,spring依赖注入的bean都是从这个单例池中去获取的,创建后完整的bean也是存放在这个单例池中的,我们这里叫它为一级缓存;
二级缓存:二级缓存从名字上可以知道是叫做早期的单例对象,就是说它是存放的是早期的单例对象,可能是不完整的;
三级缓存:存放的是我们的实例化后的原始对象,就是存放一个刚刚被创建出来的原始对象。
首先我们的一个对象被创建出来过后会放入三级缓存中,移除二级缓存,三级缓存存放的是beanName对应的一个lambad表达式,这个表达式封装了刚刚实例化出来的对象、bean对应的BeanDefinition、beanName封装在一个ObjectFactory中,所以这个时候存放到三级缓存中的只是一个lambad表达式,当调用ObjectFactory的getObject的时候才会去执行lambad表达式,也就是方法的调用; 如果整个过程中没有出现循环依赖,那么永远不会使用到二级缓存,而且三级缓存也只是存放了实例化后封装的对象,也不会去使用;
当出现了循环依赖的时候,那么这个时候就会启用二级和三级缓存,首先从三级缓存取出对象,然后执行getObject,将返回的对象(如果出现了循环依赖,而又开启了aop,返回的是一个代理对象)放入二级缓存,然后移除三级缓存;到这里可以得到一个知识点就是二级缓存和三级缓存是一对儿存在的,就是一个对象要么在二级缓存,要么在三级缓存,他们两个是成对出现的,存入二级缓存的时候,移除三级缓存,存入三级缓存的时候,移除二级缓存
@Component
public class AService {
@Autowired
private BService bS;
}
@Component
public class BService {
@Autowired
private AService aS;
}
上面这两个Bean是相互依赖的,也就是循环依赖的,加入spring首先扫描到是AService,那么AService的过程如下:
AService创建过程:
-
实例化AService,得到AService的原始对象 aS,将实例化后的对象aS放入—>三级缓存
-
填充aS中的bS->去容器找bS->找不到->进入创建BService的流程—>将返回的bS对象复制给AService中的bS;
-
填充其他属性;
-
初始化bean;
-
放入一级缓存单例池。
-
移除bS在二级、三级缓存中的对象
BService创建过程: