1、什么是循环依赖
// 假设有类AService,其中有成员属性Bservice
@Component
class AService{
@Autowired
private Bservice bservice;
.....
}
// 假设有类BService,其中有成员属性Aservice
@Component
class BService{
@Autowired
private Aservice aservice;
.....
}
Spring在创建AService这个bean时,会发现它依赖于BService这个bean;随后Spring会去创建BService这个bean,但是此时Spring会发现BService这个bean,依赖于AService这个bean,随后她又会去创建AService这个bean,以此循环反复。因此,要解决这个问题,我们首先需要知道Bean的创建过程。
2、Bean的创建过程
Bean创建过程会走以下几个步骤:
bean的实例化 -> 属性填充(依赖注入) -> 初始化操作(动态代理创建之类)-> 加入到单例池中
因此,从这个步骤,我们可以初步确定,Bean循环依赖会发生在“属性填充”这个过程。以上面代码为例:
1、Aservice实例化
2、Bservice属性填充
2.1 Bservice存于单例池中
2.1.1 取出Bservice的bean
2.1.1 返回
2.2 Bervice不存于单例池中,那么创建Bservice的bean
2.2.1 Bservice实例化
2.2.2 属性填充(这里发现依赖Aservice的bean,但是单例池中并没有Aservice的bean,会触发Aservice的bean创建。进而引起循环)
2.2.3 初始化操作
2.2.4 加入单例池
3、其他属性的填充
4、初始化
5、放入单例池中
3、循环依赖的解决思路
1、首先需要能判定循环依赖的出现。Spring的做法是在开始创建一个单例 Bean 时,会将该 Bean 的名称添加到creatingSet集合中,即
private final Set<String> singletonsCurrentlyInCreation
当Bservice的bean被创建时,发现存在Aservice的依赖,那么会先去creatingSet判断Aservice是否处于正在创建的情况,如果是,那么就可能出现循环依赖。
2、打破循环依赖。打破循环依赖的直观做法就是给循环链添加一个出口。而Spring的做法是:Bean在创建时,若发现依赖某个bean,且这个bean不存于单例池中以及这个bean的名称存于creatingSet,那么提前对所依赖的bean进行初始化。以上面的过程为例子:
0、bean的名称存于creatingSet中
1、Aservice实例化
2、Bservice属性填充
2.1 Bservice存于单例池中
2.1.1 取出Bservice的bean
2.1.1 返回
2.2 Bervice不存于单例池中,那么创建Bservice的bean
2.2.1 Bservice实例化
2.2.2 属性填充:判断Aservice的bean是否存于单例池且 bean的名称是否存于creatingSet中 -> 若是,则对Aservice的bean进行提前初始化(例如动态代理对象创建)。
2.2.3 初始化操作
2.2.4 加入单例池
3、其他属性的填充
4、初始化(需要判断是否已经进行了提前初始化,如果是的话,那么不需要再进行初始化)
5、放入单例池中
然而,上述过程还有问题:
1、提前初始化需要使用到相应的bean对象,例如创建动态代理需要有目标类,应该去哪里找?
2、假设Aservice的属性注入不止有Bservice属性,可能还有Cservice属性,并且Cservice也依赖于Aservice。那么在Aservice的bean创建过程中,除了Bservice属性的依赖注入,可能还有Cservice属性的依赖注入。那么在创建Cservice的bean时,Cservice的Aservice属性字段也应该依赖Aservice的bean,即也需要对Aservice的bean进行提前初始化。此时就出现Bservice的bean和Cservice的bean所依赖的Aservice的提前初始化bean不是同一个bean,不满足单例。
4、三级缓存机制
首先,一级缓存 就是单例池,用于存放完全初始化好的单例 Bean。他是一个Map,key为bean的名称,object对应bean。所以这也可以知道为什么bean的名称不能相同了。对应源码:
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
其次,当出现循环依赖时,需要使用Aservice的bean进行提前初始化,就需要对Aservice的实例化(注意不是初始化,要注意区分)后的bean保存起来,以便后续在Bservice的bean的属性注入阶段进行提前初始化。但是,源码里面,它并不是将Aservice的实例化对象直接保存的,而是保存Aservice的bean初始化对应的lambda表达式。这样获取某个bean时,就可以直接根据lambda表达式得到它初始化后的bean,而不是仅实例化后的bean。这就是三级缓存。对应相关源码:
// 这个是用于保存bean所对应的初始化lambda表达式,也就是三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
// ObjectFactory<?>就是函数式接口
@FunctionalInterface
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
// 在创建bean的时候,都会将这个bean名称,以及初始化操作保存到singletonFactories 中
this.addSingletonFactory(beanName, () -> this.getEarlyBeanReference(beanName, mbd, bean));
// 这个是初始化操作,对当前bean进行不断的后处理,即初始化过程
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
for(SmartInstantiationAwareBeanPostProcessor bp :
this.getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
而二级缓存,就是来存放提前初始化后的bean的。对应代码如下:
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
固有:
// 一级缓存,即单例池;多线程下存在并发冲突,因此ConcurrentHashMap
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
// 三级缓存;单线程写入,单线程读取;所以HashMap
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
// 二级缓存;多线程读写,因此是ConcurrentHashMap
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
5、通过构造方法进行依赖注入所出现的循环依赖问题
// 假设有类AService,其中有成员属性Bservice
@Component
class AService{
private Bservice bservice;
AService(Bservice bservice){
this.bservice = bservice;
}
.....
}
// 假设有类BService,其中有成员属性Aservice
@Component
class BService{
private Aservice aservice;
BService(Aservice aservice){
this.aservice= aservice;
}
.....
}
之所以三级缓存不能解决构造方法依赖注入的循环依赖问题,是因为AService和BService在实例化的时候就出现循环依赖了,连实例化都完成不了,更不可能依靠后续的三级缓存来解决循环依赖问题了。
解决思路:
1、给上面代码加一个无参数的构造方法;虽然解决了循环依赖,但是没能依赖注入。
2、使用@Lazy注解构造方法。这样在依赖注入的时候,spring会自动创建一个依赖的代理对象来代替依赖的bean;这样,后续AService对依赖的BService的bean的操作,都会打到BService的bean的代理类,由他找到bean容器中的BService的bean,再调用其对应的方法。
6、总结
一级缓存是单例池,用于存储最终的bean;二级缓存是提前曝露初始化后bean,以供后续属性赋值的依赖注入,保证单例;三级缓存提供了循环依赖的出口,提供提前初始化后的bean;此外,还需要借助creatingSet来保存那些bean正常创建,用于循环依赖的判断;earlyProxyReferences(文章没提到)可以用判断是否需要进行初始化。