【源码】Spring —— BeanFactory 解读 4 关于循环依赖
本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
在 BeanFactory 允许 循环依赖(默认允许)的情况下,Spring 是借助 三层缓存 来巧妙解决 循环依赖 问题的,本章节结合实例及文字链路描述总结下该过程
循环依赖
在 Spring 容器中,倘若 单例A 注入属性 单例B,单例B 也注入属性 单例A,我们称这种情况为 循环依赖,如以下示例
@Component
public class A {
@Autowired
B b;
}
@Component
public class B {
@Autowired
A a;
}
容器在启动时,调用 AbstractBeanFactory#getBean 方法创建对应的 bean,我们从 A 入手
A
- 试图从 DefaultSingletonBeanRegistry 的
单例池中获取,此时获取必然为null - 开始创建对应的
bean,调用AbstractAutowireCapableBeanFactory#createBean - 在判断未短路 生命周期 后调用
AbstractAutowireCapableBeanFactory#doCreateBean - 调用
AbstractAutowireCapableBeanFactory#createBeanInstance创建对应的 实例对象 - 重要的一步,我们默认允许 循环依赖,此时会创建一个对应的 单例工厂,将其加入 DefaultSingletonBeanRegistry 的二级缓存
singletonFactories - 此时调用
AbstractAutowireCapableBeanFactory#populateBean进行A的 属性填充,填充其属性B时,开始了B的创建
B
- 试图从 DefaultSingletonBeanRegistry 的
单例池中获取,此时获取必然为null - 开始创建对应的
bean,调用AbstractAutowireCapableBeanFactory#createBean - 在判断未短路 生命周期 后调用
AbstractAutowireCapableBeanFactory#doCreateBean - 调用
AbstractAutowireCapableBeanFactory#createBeanInstance创建对应的 实例对象 - 重要的一步,我们默认允许 循环依赖,此时会创建一个对应的 单例工厂,将其加入 DefaultSingletonBeanRegistry 的二级缓存
singletonFactories - 此时
AbstractAutowireCapableBeanFactory#populateBean进行B的属性填充,填充其属性A时,开始了A的创建
A^
- 试图从 DefaultSingletonBeanRegistry 的 单例池 中获取,此时虽然获取不到单例
A,但是可以获取到第一步骤中暴露的A的 单例工厂,从中获取实例并加入 三级缓存(因为从 单例工厂 中获取实例可能会是一个很吃性能的操作,比如 AOP代理) - 获取到
A的实例,返回注入给B
B^
- 属性填充 完成
- 调用
AbstractAutowireCapableBeanFactory#initializeBean初始化B B创建完成,并加入 单例池- 返回注入给
A
A^^
- 属性填充 完成
- 调用
AbstractAutowireCapableBeanFactory#initializeBean初始化A A创建完成,并加入 单例池- 循环依赖 解决
总结
一级缓存:即我们所谓的单例池,这里缓存的就是所有单例成品,获取时如果存在,直接返回即可二级缓存:缓存工厂,这里缓存的是用于创建对应实例的工厂类三级缓存:上述工厂创建的实例会被缓存在此处,因为工厂创建实例有可能会十分消耗资源:比如创建代理对象
在最新的 Spring Boot 2.6 版本中,循环依赖是默认关闭的,因此可以看出虽然 Spring
支持循环依赖,但本质上还是不建议的
因此,代码的设计和编写上应该尽量避免循环依赖:
业务设计上我们可以尽量避免如上场景的出现:避免 AB 互相引用、避免代理类如事务场景的自
引用 等
代码设计上我们也有很多方法:比如 引入中间层 或者 注入 ApplicationContext 实例
(不过这又会造成和 Spring 框架的耦合)