Spring通过三级缓存机制来解决单例Bean的Setter或字段注入类型的循环依赖问题。以下是Spring解决循环依赖的核心流程:
1. 三级缓存介绍
Spring容器为了解决循环依赖,维护了以下三个缓存:
- 一级缓存(singletonObjects):已完全初始化的单例Bean。
- 二级缓存(earlySingletonObjects):早期暴露的Bean(未完全初始化,但已注入必要属性)。
- 三级缓存(singletonFactories):存储创建Bean的工厂,允许获取Bean的代理对象。
2. 解决循环依赖的关键流程
以下是Spring处理循环依赖的步骤:
-
创建Bean实例:
Spring在实例化一个Bean时,首先会在一级缓存中查找。如果未找到,会尝试通过三级缓存提前暴露一个工厂,用于生成当前正在创建的Bean的早期引用。 -
将早期引用暴露到三级缓存:
Spring在Bean实例化之后,尚未填充属性之前,会将Bean的ObjectFactory(一个创建Bean早期引用的工厂)存入三级缓存。 -
填充依赖:
在填充Bean的依赖时,如果发现需要依赖另一个Bean且该Bean正在创建中,Spring会从三级缓存中获取早期引用,并注入到当前Bean中。 -
完成初始化:
当Bean完成属性注入并经过初始化回调后,Spring会将完全初始化的Bean移入一级缓存,同时从二级和三级缓存中移除该Bean的引用。
3. 示例代码
以循环依赖为例:
@Component
public class BeanA {
@Autowired
private BeanB beanB;
}
@Component
public class BeanB {
@Autowired
private BeanA beanA;
}
Spring解决的流程:
- 初始化BeanA,发现依赖BeanB;
- 开始初始化BeanB,发现依赖BeanA;
- 从三级缓存中获取BeanA的早期引用,将其注入到BeanB;
- BeanB完成初始化,存入一级缓存;
- 返回继续完成BeanA的初始化。
4. 注意事项
-
仅适用于单例作用域:
Spring的三级缓存机制仅适用于单例Bean。Prototype作用域的Bean不受容器管理,无法提前暴露早期引用,因此无法解决循环依赖。 -
构造器注入无法解决:
构造器注入要求在实例化时提供完整依赖,而此时无法提前暴露引用,Spring会直接抛出BeanCurrentlyInCreationException。
5. 如何避免循环依赖
虽然Spring能够解决部分循环依赖,但实际开发中,建议通过以下方式避免:
- 优化设计,拆分循环依赖。
- 使用@Lazy懒加载,延迟依赖的注入时机。
- 引入接口或事件机制,解耦Bean之间的直接依赖。
总结:
Spring通过三级缓存机制解决了Setter和字段注入的循环依赖问题,而构造器注入和Prototype作用域需要开发者自行设计规避。