什么是Spring循环依赖
首先,有两个类分别为A和B,类A中有一个类型为B的属性,类B中有一个类型为A的属性。见下图:
@Component
public class AService {
@Autowired
private BService bService;
public void test() {
System.out.printlen(bService);
}
}
@Component
public class BService {
@Autowired
private AService aService;
}
创建A的bean时的生命周期如下:
-
通过构造函数实例化一个AService对象
-
给对象中的bService(是一个类)属性赋值(依赖注入,从单例池Map找BService的bean对象,找不到就创建一个)
因为单例池中找不到BService的bean对象,则创建BService的bean:
- 通过构造函数实例化一个BService对象
- 给对象中的aService属性赋值
- 其他步骤
- 放入单例池Map
-
其他步骤(对第一步的AService对象进行初始化,AOP等操作)
-
将完整的AService对象放入单例池Map
通过上面的步骤可知,在AService的bean创建过程可以发现,如果在依赖注入的过程中发现单例池中找不到BService的bean,那么就尝试创建BService的bean对象,创建的过程中,依赖注入这一步又需要从单例池中查找AService的bean对象,找不到AService的bean,那么尝试创建AService的bena,反复循环…这就是Spring的循环依赖,Spring内部通过三级缓存解决了循环依赖。
PS:有时候启动Spring项目时会报循环依赖的错误,这是因为用到了高版本的SpringBoot,默认不开启支持循环依赖,那么可以将相关属性(allowCircularReferences)设置为true来支持循环依赖。
如何解决循环依赖
可以通过三级缓存来解决。通过以下几个问题的逐步分析来了解三级缓存:
问题1-循环依赖
即前面所讲的Spring循环依赖问题。
解决问题
解决对策:1. 加一个ZhouyuMap来解决。
加ZhouyuMap之后,AService的创建生命周期见下图:
问题2-无法处理有加强方法的AService
但是新的问题来了:
- 生成的BService的bean对象里面的AService属性赋的不是一个真正的bean对象,而是一个刚new好的AService普通对象(没有做依赖注入)。
- 还有一个情况要考虑,就是如果AService有加强方法(AOP)的话,最终生成的bean对象实际上是一个代理对象bean,并将其放入单例池。同时,BService的AService属性是一个AService普通对象,而不是想要的AService代理对象,这也是一个问题。
存在加强方法的AService的创建生命周期如下,存在上面所述的两个问题:
解决问题
为了在创建BService的时候,给aService属性赋一个AService代理对象,只能考虑将AService的创建生命周期中“创建代理对象”的第三步提前,见下图:
判断存在循环依赖
注意,提前创建代理对象前,需要进行判断AService是否存在循环依赖这一步骤,见下图的问号处:
“判断AService是否存在循环依赖”这一步骤在AService的创建生命周期的第二步也会发生:执行到给aService属性赋值,在单例池中查找AService的bean时,得知AService正在创建中,那么到这里就判断出现了循环依赖,如下图:
如下图,加个set集合CreatingSet保存正在创建的bean,然后在给aService属性赋值这一步就能查阅set集合,最后判断出AService正在创建中,得出存在循环依赖:
接着:
- 将Aservice创建生命周期第一步中“AOP—>创建AService代理对象”的操作移到第二步中“AService正在创建中”后面
- 将Aservice创建生命周期第一步中“放入ZhouyuMap”操作和第二步中“ZhouyuMap—>AService普通对象”操作直接去掉
形成下图:
问题3
那么新的问题来了:
- 上图中红框处的“其他步骤(内容包括创建代理对象)”就不能执行了,需要加一个判断(也就是问号),怎么判断?
- 给aService属性赋值过程中产生的AService代理对象也要加到单例池中,怎么加?所以就有这两个问题。
解决问题-加二级缓存Map
解决对策,见下图:
- 第二步中“AService正在创建中”后面,AOP创建的代理对象或者非AOP创建的普通对象(“判断AOP或者非AOP,然后创建代理对象或者普通对象”的步骤,都在一个方法里定义)加入二级缓存Map
- 然后“其他步骤”判断是AOP,就不再创建代理对象,然后到“第四步”的时候,从二级缓存读取bean对象放到单例池中。
问题4
接下来看另一种情况:
- 加了带aService属性的CService类,同时AService类又加了cService属性,那么用上图的步骤,会出现给cService属性赋值的时候,重复创建AService代理对象的情况
@Component
public class AService {
@Autowired
private BService bService;
@Autowired
private CService cService;
public void test() {
System.out.printlen(bService);
}
}
@Component
public class CService {
@Autowired
private AService aService;//AService代理对象
}
解决问题
解决对策:
- 见下图,改成红框处的步骤,先判断二级缓存有没有,没有就创建对象再放入二级缓存,即可解决前面的问题。
最后完整的图如下,只有一级缓存Map和二级缓存Map,可以处理无加强方法的AService和有加强方法的AService:
问题5
但是,如上图所示:
- 创建BService的bean对象时,给aService属性赋值的时候,二级缓存中没找到AService的bean对象,然后判断是AService类是非AOP的时候,需要拿到AService普通对象,那么这里需要从哪里拿呢?
解决问题-加三级缓存Map
解决对策:
- 见下图红框处,在第一步的最后加了第三级缓存ZhouyuMap,用来用来存储未初始化的AService普通对象,不管有没有可能出现循环依赖,都会用到zhouyuMap进行缓存。ZhouyuMap的用途:
- 创建BService时,给aService属性赋值时,AService非AOP的情况下获取AService普通对象;
- 创建BService时,给aService属性赋值时,AService是AOP的情况下创建代理对象时也会用到AService普通对象。
最后完整的图如下:
其中lambda指的是,从三级缓存获取普通AService对象,根据AService是否为AOF来创建代理对象或者普通对象。
综上,通过三级缓存解决了所有存在的问题。
总结
通过对上面Spring循环依赖存在的所有问题的逐步分析,可了解到三级缓存是如何解决循环依赖问题的。
有三级缓存的情况下,AService的创建生命周期:
三级缓存:
-
第三级缓存:zhouyuMap,在源码中的变量名为singletonFactories,保存没有初始化(依赖注入)的普通对象。
用于在创建AService的bean对象的过程中:1创建BService时,给aService属性赋值时,AService非AOP的情况下获取AService普通对象;2创建BService时,给aService属性赋值时,AService是AOP的情况下创建代理对象时也会用到AService普通对象。
-
第二级缓存:二级缓存Map,在源码中的变量名为earlysingletonObjects,之所以加early是因为二级缓存保存的是没有初始化(依赖注入)的代理对象或者普通对象。
用于在创建AService的bean对象的过程中,要创建BService的bean对象,BService的bean对象的创建过程中又需要AService的bean对象(未完成初始化的代理对象或者普通对象),那么就将AService的bean对象存在二级缓存中。
-
第一级缓存:单例池Map,键值对形式为<beanName, Bean对象>,在源码中的变量名为singletonObjects,用于保存真正的bean对象(完成创建的生命周期)
总结一下:
- 如果AService没有AOP,那么用第一级缓存和第三级缓存,因为二级缓存存的就是普通对象,跟第三级缓存没有区别,所以二级缓存等于第三级缓存就可以。
- 如果AService有AOP,那么二级缓存存的是代理对象,就需要第一二三级缓存。
Spring循环依赖相关源码待研究。
参考资料
【图灵学院】周瑜老师2小时讲透三级缓存解决Spring循环依赖底层原理解析,让你面试少走99%的弯路_哔哩哔哩_bilibili P1-6
摘抄
A/B在三级缓存中的迁移说明:
非AOP
- 先去一级缓存寻找 A ,没有去创建 A, 然后 A 将自己放到三级缓存中,初始化的时候需要 B ,去创建 B
- B 实例化同理 A(B 放入到了三级缓存),B 初始化的时候发现需要 A ,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了 A 然后把三级中的 A 放入二级缓存里面,并删除三级缓存中的 A
- B 顺利初始化完毕,将自己放到一级缓存里面(此时 B 里面的 A 依然是创建中的状态),再删除三级缓存中的 B 和尝试去删除二级缓存中的 B (此时二级缓存中只有 A )
- 然后回来接着创建 A ,此时 B 已经创建结束,直接从一级缓存里面拿到 B ,然后完成 A 创建,再从二级缓存中获取 A 对象, 并将 A 自己放入到一级缓存里面,再删除三级缓存中的 A 和删除二级缓存中的 A
AOP
- 先去一级缓存寻找 A ,没有去创建 A, 然后 A 将自己的 ObjectFactory(lambda) 放入到三级缓存中,初始化的时候需要 B ,去创建 B
- B 实例化同理 A (B 将自己的工厂对象( lambda )放入到了 三级缓存),B 初始化的时候发现需要 A ,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了 A 的 ObjectFactory
- 然后调用 ObjectFactory.getObject() 方法最终会调用 getEarlyBeanReference() 方法,返回一个 A 的代理对象,然后把 proxyA 放入二级缓存里面,并删除三级缓存中的 ObjectFactoryA
- B 顺利赋值完毕,通过 populateBean 返回 ,然后调用 initializeBean 方法进行初始化操作
- 然后回来接着创建 A ,此时 B 已经创建结束,直接从一级缓存里面拿到 proxyB ,完成注入操作及初始化。
- 由于完成注入操作及初始化返回的是 A 对象,不是 proxyA。再从二级缓存中获取到 proxyA 然后返回 proxyA
- 最后将 proxyA 放入到一级缓存中,再从二级缓存中删除 proxyA。
来源: