深入剖析Spring如何解决循环依赖
目录
- 一、什么是循环依赖
- 二、Spring循环依赖的三种情况
- 三、Spring解决循环依赖的核心思想
- 四、三级缓存详解
- 1. 三级缓存结构
- 2. Bean创建与三级缓存交互流程
- 3. 循环依赖解决示例
- 五、源码分析
- 六、为什么构造器循环依赖无法解决
- 七、为什么原型模式的循环依赖无法解决
- 八、Spring解决循环依赖的局限性
- 九、最佳实践
- 十、总结
一、什么是循环依赖
循环依赖(Circular Dependency)是指两个或多个Bean相互依赖,形成一个闭环的情况。例如:
@Service public class AService { @Autowired private BService bService; } @Service public class BService { @Autowired private AService aService; }
上述代码中,AService依赖BService,而BService又依赖AService,形成了循环依赖。
二、Spring循环依赖的三种情况
构造器循环依赖:通过构造器注入形成的循环依赖,Spring无法解决,会直接抛出BeanCurrentlyInCreationException
Setter循环依赖(单例模式):通过setter方法注入形成的循环依赖,Spring可以解决
原型模式(prototype)循环依赖:scope为prototype的bean形成的循环依赖,Spring无法解决三、Spring解决循环依赖的核心思想
Spring通过三级缓存机制来解决单例模式下的Setter循环依赖问题。核心思想是:
提前暴露未完全初始化的Bean实例:在Bean创建过程中,在完成实例化后、初始化前,将Bean的引用提前暴露
分级缓存:使用三级缓存存储不同状态的Bean,确保依赖注入时能获取到正确的引用
四、三级缓存详解
1. 三级缓存结构
/** 一级缓存:存放完全初始化好的Bean */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** 二级缓存:存放原始的Bean对象(尚未填充属性) */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); /** 三级缓存:存放Bean工厂对象,用于生成原始Bean对象 */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
2. Bean创建与三级缓存交互流程
js1.创建Bean实例:通过反射调用构造器创建Bean实例
2.放入三级缓存:将Bean包装成ObjectFactory并放入三级缓存singletonFactories
3.属性填充:填充Bean的属性,此时如果发现依赖其他Bean
- 从一级缓存singletonObjects获取
- 如果不存在,尝试从二级缓存earlySingletonObjects获取
- 如果还不存在,从三级缓存singletonFactories获取ObjectFactory并生成Bean,然后放入二级缓存
4.初始化:执行初始化方法(@PostConstruct等)
5.放入一级缓存:将完全初始化好的Bean放入一级缓存,删除二、三级缓存中的记录
3. 循环依赖解决示例
以AService和BService的循环依赖为例:
1.开始创建AService
- 实例化AService
- 将AService的ObjectFactory放入三级缓存
2.填充AService属性时发现需要BService
- 开始创建BService
- 实例化BService
- 将BService的ObjectFactory放入三级缓存
3.填充BService属性时发现需要AService
- 从一级缓存获取AService → 不存在
- 从二级缓存获取AService → 不存在
- 从三级缓存获取AService的ObjectFactory → 存在,生成AService早期引用并放入二级缓存
- 将AService早期引用注入BService
4.BServic编程客栈e继续完成属性填充和初始化
5.BService创建完成,放入一级缓存
6.AService获得完全初始化的BService引用
7.AService继续完成属性填充和初始化
8.AService创建完成,放入一级缓存
五、源码分析
关键源码在DefaultSingletonBeanRegistry类中:
protected Objecandroidt getSingleton(String beanName, boolean allowEarlyReference) { // 1. 先从一级缓存获取 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanNa编程客栈me)) { synchronized (this.singletonObjects) { // 2. 从二级缓存获取 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 3. 从三级缓存获取ObjectFactory ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); // 将三级缓存提升到二级缓存 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
六、为什么构造器循环依赖无法解决
时序问题:构造器注入发生在实例化阶段,此时Bean尚未创建完成,无法提前暴露引用
缓存机制不适用:三级缓存机制依赖于在实例化后、初始化前暴露引用,而构造器注入时连实例都还没完全创建
七、为什么原型模式的循环依赖无法解决
生命周期不同:原型模式每次获取都创建新实例,Spring不缓存原型Bean
无法提前暴露引用:没有缓存机制支持,无法在创建过程中共享未完成初始化的引用
八、Spring解决循环依赖的局限性
只能解决单例模式的Setter注入循环依赖
大量使用循环依赖会导致代码结构混乱,增加维护难度
某些AOP场景下可能出现问题(需要特殊处理)
九、最佳实践
避免循环依赖:通过设计模式(如中介者模式、观察者模式)重构代码
使用Setter注入替代构造器注入:如果必须使用循环依赖
使用@Lazy注解:延迟加载依赖的Bean
@Service public class AService { @Autowired @Lazy private BService bService; }
使用ApplicationContextAware接口:手动获取依赖Bjavascriptean
十、总结
Spring通过三级缓存机制巧妙地解决了单例模式下Setter注入的循环依赖问题,但其本质上是一种妥协方案。良好的系统设计应当尽量避免循环依赖,保持清晰的依赖关系。理解Spring解决循环依赖的机制,有助于我们更好地使用Spring框架,并在遇到相关问题时能够快速定位和解决。
到此这篇关于深入剖析Spring如何解决循环依赖的文章就介绍到这了,更多相关Spring解决循环依赖内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论