- 什么是循环依赖? 循环依赖是指两个或多个Bean相互依赖,形成一个闭环。例如:
A依赖B,B也依赖A:A -> B -> A
更复杂的循环:A -> B -> C -> A
如果没有特殊处理,这种依赖关系会导致无限递归,最终栈溢出。
- Spring解决循环依赖的核心原理 Spring解决循环依赖的核心在于 三级缓存 和 Bean的“实例化”与“属性注入”分离 的创建过程。
关键点一:Bean创建的生命周期(简化版) 对于一个单例Bean,Spring的创建过程主要分为以下几个步骤:
实例化: 通过反射调用构造函数创建一个对象(此时对象属性均为空,可以理解为new A())。
属性填充: 为刚刚创建的对象注入其依赖的Bean(即调用setter方法或填充@Autowired标记的字段)。
初始化: 调用@PostConstruct方法、执行InitializingBean接口的afterPropertiesSet方法等。
放入单例池: 将完全初始化好的Bean放入单例池(一级缓存),之后程序就可以获取到它了。
解决循环依赖的关键就在于将“实例化”和“属性填充”这两个步骤分开了。 你可以在A对象刚被实例化(但属性还为空)的时候,就提前暴露这个“不完整”的引用,让其他Bean(比如B)能够先引用到它。
关键点二:三级缓存 Spring容器内部维护了三个Map,也就是我们常说的“三级缓存”:
缓存级别 缓存名称 存储内容 说明 一级缓存 singletonObjects 完整的单例Bean 存放已经完全初始化好的Bean。程序正常获取Bean的地方。 二级缓存 earlySingletonObjects 早期的Bean引用 存放刚刚实例化,但还未进行属性填充的Bean的代理对象或原始对象。用于解决循环依赖。 三级缓存 singletonFactories 单例对象工厂 存放一个ObjectFactory工厂对象。当发生循环依赖时,会调用这个工厂的getObject()方法来获取一个早期的Bean引用。 3. 解决循环依赖的流程(以 A → B → A 为例) 让我们一步步跟踪Spring是如何解决这个循环依赖的:
开始创建A
首先,Spring准备创建Bean A。在调用A的构造函数之前,会先将一个能获取“早期A引用”的ObjectFactory工厂放入三级缓存(singletonFactories)中。
实例化A
通过反射new A()创建A的实例。此时A的属性b是null。
为A填充属性B(发现依赖B)
Spring发现A依赖B,于是尝试从容器中获取Bean B。
开始创建B
容器中还没有B,于是Spring开始创建B。同样,在实例化B之前,会将一个能获取“早期B引用”的ObjectFactory工厂放入三级缓存。
实例化B
通过反射new B()创建B的实例。此时B的属性a是null。
为B填充属性A(发现循环依赖!)
Spring发现B依赖A,于是尝试从容器中获取Bean A。
获取A的流程如下:
查找一级缓存(singletonObjects): 没有,因为A还没创建完。
查找二级缓存(earlySingletonObjects): 没有。
查找三级缓存(singletonFactories): 找到了! 在步骤1中存入的ObjectFactory。
执行工厂方法: Spring调用这个ObjectFactory的getObject()方法。这个方法会返回一个A的早期引用(可能是原始对象,也可能是AOP代理对象)。
升级缓存: 将得到的早期引用A从三级缓存移动到二级缓存(同时删除三级缓存中的工厂)。
此时,B成功获取到了A的早期引用(虽然不完整,但地址已经确定),并将其注入到自己的属性a中。
B完成创建
B顺利完成了属性填充和初始化。
B被放入一级缓存,同时清理二级和三级缓存中关于B的条目。
A继续属性填充
此时,步骤3中A获取B的请求终于返回了已经创建好的B(从一级缓存中获取)。
A将B注入到自己的属性b中。
A完成创建
A顺利完成了属性填充和初始化。
A被放入一级缓存,同时清理二级缓存中关于A的条目。
至此,循环依赖被成功解决,A和B都成为了完整的Bean。