Spring循环依赖详解
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 {@Autowiredprivate ServiceB serviceB;
}@Component
public class ServiceB {@Autowiredprivate 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. 从三级缓存获取ObjectFactoryObjectFactory<?> 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@Autowiredprivate ServiceB serviceB;
}
- 使用Setter注入代替构造器注入:
@Component
public class ServiceA {private ServiceB serviceB;@Autowiredpublic void setServiceB(ServiceB serviceB) { this.serviceB = serviceB; }
}
- 使用@DependsOn指定初始化顺序:
@Component
@DependsOn("serviceB")
public class ServiceA {@Autowiredprivate ServiceB serviceB;
}
- 使用ApplicationContextAware手动获取Bean:
@Component
public class ServiceA implements ApplicationContextAware {private ApplicationContext context;@Overridepublic 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 {@Beanpublic ServiceA serviceA(@Lazy ServiceB serviceB) {return new ServiceA(serviceB);}
}
六、最佳实践建议
- 尽量避免循环依赖,这是代码设计问题而非技术问题
- 优先使用构造器注入(Spring官方推荐)
- 对于不可避免的循环依赖,使用Setter/Field注入
- 在大型项目中,使用模块化设计减少循环依赖
- 定期使用架构分析工具检测循环依赖