Spring IoC 如何解决循环依赖
循环依赖是指两个或多个 Bean 相互依赖,形成闭环的情况(如 A 依赖 B,B 又依赖 A)。Spring IoC 容器通过巧妙的机制解决了这个问题。
解决循环依赖的核心机制
Spring 主要通过 三级缓存 来解决循环依赖问题:
-
一级缓存(singletonObjects):存储完全初始化好的单例 Bean
-
二级缓存(earlySingletonObjects):存储提前暴露的原始 Bean(未填充属性)
-
三级缓存(singletonFactories):存储 Bean 的 ObjectFactory,用于生成原始 Bean 的代理对象
解决流程示例
以 A 依赖 B,B 依赖 A 为例:
-
创建 A 的实例
-
调用 A 的构造器创建原始对象
-
将 A 的 ObjectFactory 放入三级缓存
-
开始填充 A 的属性
-
-
发现 A 依赖 B
-
尝试获取 B 的实例
-
如果 B 不存在,开始创建 B
-
-
创建 B 的实例
-
调用 B 的构造器创建原始对象
-
将 B 的 ObjectFactory 放入三级缓存
-
开始填充 B 的属性
-
-
发现 B 依赖 A
-
尝试获取 A 的实例
-
从三级缓存中找到 A 的 ObjectFactory
-
通过 ObjectFactory 获取 A 的早期引用(可能是原始对象或代理对象)
-
将 A 的早期引用放入二级缓存,并从三级缓存移除
-
-
B 完成初始化
-
B 获得 A 的早期引用后完成属性注入
-
将初始化完成的 B 放入一级缓存
-
-
A 完成初始化
-
A 获得初始化完成的 B 后完成属性注入
-
将初始化完成的 A 放入一级缓存
-
从二级缓存移除 A
-
解决循环依赖的限制
Spring 只能解决 单例作用域 且 通过属性注入 的循环依赖,以下情况无法解决:
-
构造器注入 导致的循环依赖
-
因为构造器注入需要在实例化时就完成依赖注入
-
此时 Bean 还未创建,无法提前暴露引用
-
-
原型作用域(prototype) 的循环依赖
-
原型 Bean 每次获取都是新实例
-
Spring 不会缓存原型 Bean 的引用
-
-
@Async 等 AOP 代理的特殊情况
-
某些代理方式可能导致循环依赖解决失败
-
最佳实践
-
尽量避免循环依赖,设计更清晰的依赖关系
-
如果必须使用循环依赖,优先使用 setter 注入而非构造器注入
-
对于必须使用构造器注入的情况,考虑重构代码结构
Spring 的这种解决方案在保持性能的同时,优雅地处理了大多数常见的循环依赖场景。
package com.gitee.dbapi.testframe; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class TestA { public void Ta(){ System.out.println("ta"); } @Autowired TestB testB; }
package com.gitee.dbapi.testframe; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class TestB { public void Tb(){ System.out.println("tb"); } @Autowired TestA testA; }
@Autowired TestA testA; @Test public void test010_retSelectByPkey() { testA.Ta(); }