Spring 循环依赖详解
一、循环依赖
1. 什么是循环依赖
循环依赖是指两个或多个Spring Bean之间形成相互依赖的闭环关系。具体表现为:
Bean A 依赖 Bean B
Bean B 依赖 Bean C
…
Bean N 又依赖 Bean A
2. 循环依赖出现的场景
循环依赖通常出现在以下场景中:
- Bean初始化阶段
- 当Spring容器启动,初始化Bean时
- 特别是使用构造器注入方式时最容易暴露
- 依赖注入时
- 通过@Autowired进行字段/方法注入时
- 使用XML配置中的或时
- 特定设计模式中
- 双向关联的业务场景(如订单-支付系统)
- 相互回调的组件设计
3. 循环依赖会带来什么问题
- 启动时抛出BeanCurrentlyInCreationException
- 错误信息:“Requested bean is currently in creation: Is there an unresolvable circular reference?”
- 每次获取bean都会尝试创建新实例
- 最终导致栈溢出(StackOverflowError)
- …
二、循环依赖的几种类型
1. Setter/Field注入循环依赖
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Component
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
2. 构造器循环依赖
@Component
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(ServiceB serviceB) { this.serviceB = serviceB; }
}
@Component
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(ServiceA serviceA) { this.serviceA = serviceA; }
}
三、Spring解决循环依赖的机制
1. 三级缓存机制(可解决注入循环依赖)
1.1 三级缓存
在初始化阶段(实例化之后),Spring使用三级缓存来解决单例Bean的循环依赖问题:
| 缓存级别 | 名称 | 存储内容 | 作用 |
|---|---|---|---|
| 一级缓存 | singletonObjects | 完全初始化好的Bean | 提供最终可用的Bean |
| 二级缓存 | earlySingletonObjects | 提前曝光的半成品Bean | 解决循环依赖 |
| 三级缓存 | singletonFactories | 对象工厂(ObjectFactory) | 生成代理对象 |
这是Spring三级缓存的部分源码
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/** Cache of singleton objects: bean name to bean instance. */
// 一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
// 三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
// 二级缓存
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
...
}
1.2 解决流程(以A→B→A为例)
- 开始创建A,实例化A(调用构造器)
- 将A的对象工厂放入三级缓存
- 填充A的属性,发现需要B
- 开始创建B,实例化B(调用构造器)
- 将B的对象工厂放入三级缓存
- 填充B的属性,发现需要A
- 从三级缓存获取A的对象工厂,生成早期引用
- 将A的早期引用放入二级缓存,删除三级缓存中的A
- B完成属性注入,初始化完成
- A得到B的引用,继续完成初始化
- A初始化完成,放入一级缓存
- B中的A引用最终指向完全初始化的A

1.3 关键源码分析
DefaultSingletonBeanRegistry类中的关键方法:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 从一级缓存查询
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
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;
}
2. 懒加载(可解决构造循环依赖)
在实例化阶段,可使用@Lazy注解通过以下方式解决构造器循环依赖:
不立即初始化依赖的Bean
创建一个代理对象作为占位符
当实际需要调用依赖Bean时才进行初始化
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(@Lazy ServiceA serviceA) {
this.serviceA = serviceA;
}
}
四、如何避免/解决循环依赖
1. 设计层面解决方案
- 重构代码:解耦相互依赖的组件
- 接口抽象:依赖接口而非具体实现
- 应用事件:使用事件机制代替直接调用
- 模板方法模式:将公共逻辑提取到父类
2. 技术层面解决方案
- 使用@Lazy延迟加载:
@Component
public class ServiceA {
@Lazy
@Autowired
private ServiceB serviceB;
}
- 使用Setter注入代替构造器注入:
@Component
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) { this.serviceB = serviceB; }
}
- 使用@DependsOn指定初始化顺序:
@Component
@DependsOn("serviceB")
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
- 使用ApplicationContextAware手动获取Bean:
@Component
public class ServiceA implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
public void doSomething() {
ServiceB serviceB = context.getBean(ServiceB.class);
// 使用serviceB
}
}
五、特殊场景处理
1. AOP代理下的循环依赖
Spring通过SmartInstantiationAwareBeanPostProcessor处理代理对象的循环依赖,关键类是AbstractAutoProxyCreator。
2. 构造器注入的替代方案
如果必须使用构造器注入,可以考虑:
@Configuration
public class MyConfig {
@Bean
public ServiceA serviceA(@Lazy ServiceB serviceB) {
return new ServiceA(serviceB);
}
}
六、最佳实践建议
- 尽量避免循环依赖,这是代码设计问题而非技术问题
- 优先使用构造器注入(Spring官方推荐)
- 对于不可避免的循环依赖,使用Setter/Field注入
- 在大型项目中,使用模块化设计减少循环依赖
- 定期使用架构分析工具检测循环依赖
7753

被折叠的 条评论
为什么被折叠?



