Spring循环依赖的解决方式详解
目录
- 什么是编程循环依赖
- Spri编程客栈ng解决循环依赖的机制
- 三级缓存结构
- 解决流程
- 详细步骤解析
- 代码层面的实现
- 循环依赖的限制
- 最佳实践
- 总结
什么是循环依赖
循环依赖是指两个或多个Bean相互依赖,形成一个闭环。例如:
@Component public class A { @Autowired 编程客栈 private B b; } @Component public class B { @Autowired private A a; }
在这个例子中,A依赖B,B又依赖A,形成了一个循环依赖链。
Spring解决循环依赖的机制
Spring通过三级缓存机制来解决循环依赖问题,具体实现如下:
三级缓存结构
- 一级缓存(singletonObjects):存储完全初始化好的Bean
- 二级缓存(earlySingletonObjects):存储提前暴露的原始Bean(尚未填充属性)
- 三级缓存(singletonFactories):存储Bean工厂,用于生成原始Bean的早期引用
解决流程
Spring解决循环依赖的核心流程如下:
详细步骤解析
创建Bean A:
- Spring容器开始创建Bean A
- 实例化A(调用构造方法)
- 将A的ObjectFactory放入三级缓存(singletonFactories)
- 准备填充A的属性
发现依赖B:
- 在填充A的属性时,发现需要注入Bean B
- 容器开始创建Bean B
创建Bean B:
- 实例化B(调用构造方法)
- 将B的ObjectFactory放入三级缓存(singletonFactories)
- 准备填充B的属性
发现依赖A:
- 在填充B的属性时,发现需要注入Bean A
- 容器尝试从一级缓存获取A(未找到)
- 从二级缓存获取A(未找到)
- 从三级缓存获取A的ObjectFactory并调用getObject()获取早期引用
- 将A的早期引用放入二级缓存,并从三级缓存移除
完成B的创建:
- 将B的属性填充完整
- 执行B的初始化后处理器
- 将完整的B放入一级缓存
- 从二级和三级缓存中移除B的相关条目
完成A的创建:
- 现在A可以获取到完整的B实例
- 将A的属性填充完整
- 执行A的初始化后处理器
- 将完整的A放入一级缓存
- 从二级缓存中移除A
代码层面的实现
Spring解决循环依赖的核心代码在DefaultSingletonBeanRegistry
类中:
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 首先检查一级缓存 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 检查二级缓存 singletonObject = this.earlySingletonObjects.get(beanName); ijsf (singletonObject == null && allowEarlyReference) { // 检查三级缓存 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); // 从三级缓存移到二级缓存 php this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
循环依赖的限制
Spring并非能解决所有类型的循环依赖,有以下限制:
构造器注入:无法解决构造器注入的循环依赖
@Component public class A { private B b; @Autowired public A(B b) { this.b = b; } } @Component public class B { private A a; @Autowired public B(A a) { this.a = a; } }
这种情况会抛出BeanCurrentlyInCreationException
原型(prototype)作用域的Bean:Spring不支持原型Bean的循环依赖
@Async方法:如果循环依赖中包含@Async方法,也可能出现问题
最佳实践
- 尽量避免循环依赖,设计时应考虑解耦
- 如果必须使用循环依赖,优先使用setter注入而非构造器注入
- 考虑使用@Lazy注解延迟加载其中一个Bean
@Component public class A { @Lazy @Autowired private B b; }
总结
Spring通过三级缓存机制巧妙地解决了setter/field注入方式的循环依赖问题。理解这一机制不仅有助于我们避免开发中的循环依赖陷阱,也能更深入地理解Spring容器的Bean生命周期管理。在实际开发中,我们应当合理设计Bean之间的依赖关系,尽量避免循环依赖,当确实需要时,也要了解其背后的原理和限制。
以上就是Spring循环依赖的解决方式详解的详细内容,更多关于Spring循环依赖的资料请关注编程客栈(www.devze.com)其它相关文章!
精彩评论