Spring循环依赖深度解析:从原理到源码,一次性搞懂
Spring是如何解决循环依赖的?三级缓存到底干了什么?构造器注入为什么不行?本文从源码层面为你彻底讲透。
一、什么是循环依赖?
循环依赖(Circular Dependency),简单来说就是两个或多个Bean之间互相持有对方的引用。就像两个人互相等待对方先开口说话,谁也迈不出第一步。
java
@Component
public class OrderService {
@Autowired
private UserService userService;
}
@Component
public class UserService {
@Autowired
private OrderService orderService;
}
上面的代码中,OrderService 依赖 UserService,UserService 又依赖 OrderService,形成了一个闭环。这种情况在实际开发中并不少见,尤其是在复杂业务系统中。
循环依赖的三种类型
| 类型 | 说明 | Spring是否支持 |
|---|---|---|
| 构造器注入循环依赖 | 通过构造方法参数互相引用 | 不支持 |
| 属性注入(Setter)循环依赖 | 通过字段或Setter方法注入 | 支持(单例模式下) |
| prototype循环依赖 | 多例模式下的循环依赖 | 不支持 |
注意: Spring默认只在「单例模式 + 属性注入」的情况下支持循环依赖。
二、Spring Bean的生命周期
要理解循环依赖的解决原理,首先必须搞懂Spring Bean的生命周期。一个Bean从无到有,大致经历以下步骤:
css
[类扫描] → [BeanDefinition] → [实例化] → [属性注入] → [初始化] → [放入单例池]
具体展开来看:
- 扫描 :Spring扫描指定包下的类,发现带有
@Component等注解的类 - 解析BeanDefinition :将类的信息(类型、作用域、懒加载等)封装为
BeanDefinition对象 - 注册BeanDefinition :将
BeanDefinition存入beanDefinitionMap - 实例化:调用构造方法创建对象(此时属性为null,还不是一个完整的Bean)
- 属性注入 :通过
@Autowired等方式为属性赋值 - 初始化 :调用Aware接口回调、
@PostConstruct、InitializingBean.afterPropertiesSet()、自定义init-method - 放入单例池 :Bean创建完成,存入
singletonObjects
关键源码入口:
java
// AbstractApplicationContext.java
public void refresh() throws BeansException {
// ...省略前置步骤...
// 步骤1: 调用BeanFactory后置处理器,完成扫描
invokeBeanFactoryPostProcessors(beanFactory);
// 步骤2: 注册Bean后置处理器
registerBeanPostProcessors(beanFactory);
// ...省略中间步骤...
// 步骤3: 实例化所有非懒加载的单例Bean
finishBeanFactoryInitialization(beanFactory);
}
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// 实例化所有单例Bean
beanFactory.preInstantiateSingletons();
}
三、三级缓存机制------Spring解决循环依赖的核心
Spring使用了三级缓存来解决单例模式下的属性注入循环依赖问题。这是理解循环依赖的关键所在。
3.1 三级缓存是什么?
Spring在DefaultSingletonBeanRegistry中定义了三个Map:
java
// DefaultSingletonBeanRegistry.java
// 一级缓存:单例池,存放完整的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:存放早期的Bean引用(已实例化,未初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:存放Bean工厂(ObjectFactory),用于生成早期Bean引用
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 正在创建中的Bean名称集合
private final Set<String> singletonsCurrentlyInCreation = ConcurrentHashMap.newKeySet();
三级缓存的作用
| 缓存级别 | 名称 | 存放内容 | 作用 |
|---|---|---|---|
| 一级缓存 | singletonObjects | 完整的Bean | 日常获取Bean的来源 |
| 二级缓存 | earlySingletonObjects | 早期Bean引用 | 解决循环依赖时临时存放 |
| 三级缓存 | singletonFactories | ObjectFactory | 生成早期Bean引用的工厂 |
3.2 三级缓存是如何协作的?
当创建Bean A时,Spring会按以下顺序工作:
- 实例化A:调用构造方法创建A的原始对象
- 提前暴露A的工厂 :将A的
ObjectFactory放入三级缓存 - 属性注入A:发现A依赖B,去获取B
- 实例化B:调用构造方法创建B的原始对象
- 提前暴露B的工厂 :将B的
ObjectFactory放入三级缓存 - 属性注入B:发现B依赖A,去获取A
- 获取A:从三级缓存中拿到A的工厂,调用工厂方法得到A的早期引用,放入二级缓存,删除三级缓存
- B属性注入完成,继续初始化B,B创建完成放入一级缓存
- 回到A,A的属性注入完成,继续初始化A,A创建完成放入一级缓存
四、源码追踪:循环依赖的解决过程
让我们通过源码一步步追踪Spring是如何解决OrderService和UserService循环依赖的。
4.1 获取Bean的入口
java
// AbstractBeanFactory.java
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType,
@Nullable Object[] args, boolean typeCheckOnly) {
// 第一步:从一级缓存中获取
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 缓存中存在,直接返回
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
// 缓存中不存在,开始创建Bean
// ...
// 标记当前Bean正在创建
beforeSingletonCreation(beanName);
try {
singletonObject = singletonFactory.getObject();
} finally {
// 移除正在创建标记
afterSingletonCreation(beanName);
}
}
// ...
}
4.2 创建Bean的核心方法
java
// AbstractAutowireCapableBeanFactory.java
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// 第一步:实例化Bean(调用构造方法)
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// 第二步:提前暴露早期引用(关键!)
boolean earlySingletonExposure = (mbd.isSingleton()
&& this.allowCircularReferences
&& isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 将ObjectFactory放入三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
Object exposedObject = bean;
try {
// 第三步:属性注入
populateBean(beanName, mbd, instanceWrapper);
// 第四步:初始化Bean
exposedObject = initializeBean(beanName, exposedObject, mbd);
} catch (Throwable ex) {
// 异常处理...
}
// 第五步:如果存在循环依赖,检查早期引用
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
}
}
return exposedObject;
}
4.3 从缓存中获取Bean
java
// DefaultSingletonBeanRegistry.java
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 从一级缓存获取(完整的Bean)
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 一级缓存没有,且Bean正在创建中
// 2. 从二级缓存获取(早期Bean引用)
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 二级缓存也没有
// 3. 从三级缓存获取(ObjectFactory)
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 调用工厂方法,获取早期Bean引用
singletonObject = singletonFactory.getObject();
// 放入二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
// 从三级缓存中移除
this.singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}
4.4 提前暴露的工厂方法
java
// AbstractAutowireCapableBeanFactory.java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
// 遍历所有SmartInstantiationAwareBeanPostProcessor
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAwareList) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
return exposedObject;
}
这个方法非常重要------它就是为什么Spring要用三级缓存而不是二级缓存的原因。通过
ObjectFactory,Spring可以在获取早期引用时对Bean进行AOP代理增强,确保注入的是代理对象而非原始对象。
五、完整的循环依赖解决流程
假设有OrderService和UserService互相依赖,完整流程如下:
第一步:创建OrderService
csharp
1. getBean("orderService")
2. 一级缓存没有 → 开始创建
3. 标记orderService正在创建 → singletonsCurrentlyInCreation.add("orderService")
4. 实例化OrderService → new OrderService()(属性userService为null)
5. 将ObjectFactory放入三级缓存 → singletonFactories.put("orderService", factory)
第二步:属性注入OrderService,发现需要UserService
scss
6. populateBean() → 需要注入userService
7. getBean("userService")
8. 一级缓存没有 → 开始创建
9. 标记userService正在创建 → singletonsCurrentlyInCreation.add("userService")
10. 实例化UserService → new UserService()(属性orderService为null)
11. 将ObjectFactory放入三级缓存 → singletonFactories.put("userService", factory)
第三步:属性注入UserService,发现需要OrderService
markdown
12. populateBean() → 需要注入orderService
13. getBean("orderService")
14. 一级缓存没有(orderService还没创建完)
15. 二级缓存没有
16. 三级缓存有!调用factory.getObject() → 得到OrderService早期引用
17. 放入二级缓存 → earlySingletonObjects.put("orderService", earlyRef)
18. 从三级缓存删除 → singletonFactories.remove("orderService")
19. 返回OrderService早期引用 → UserService属性注入成功
第四步:UserService创建完成
markdown
20. UserService初始化完成
21. 放入一级缓存 → singletonObjects.put("userService", userService)
22. 清除二级/三级缓存中userService的记录
第五步:回到OrderService,继续创建
markdown
23. OrderService属性注入成功(userService已有值)
24. OrderService初始化完成
25. 放入一级缓存 → singletonObjects.put("orderService", orderService)
26. 清除二级缓存中orderService的记录
六、为什么需要三级缓存?二级不够吗?
这是面试中最常被问到的问题。
如果只用二级缓存(直接存早期Bean引用):
java
// 假设只有两级缓存
addSingletonFactory(beanName, bean); // 直接存原始对象
问题: 如果OrderService需要被AOP代理(比如加了@Transactional),那么注入给UserService的应该是代理对象,而不是原始对象。但如果二级缓存直接存原始对象,UserService拿到的就不是代理对象。
三级缓存通过ObjectFactory解决这个问题:
java
// 实际的三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
getEarlyBeanReference方法会遍历所有的SmartInstantiationAwareBeanPostProcessor,如果存在AOP切面,就会返回代理对象;如果没有,就返回原始对象。
简单来说:三级缓存的ObjectFactory是为了处理AOP代理的情况,确保注入的是正确的对象(可能是代理对象)。
补充: 如果没有AOP代理的需求,理论上二级缓存就够了。Spring之所以统一使用三级缓存,是为了兼顾AOP场景。
七、常见问题与解决方案
问题1:构造器注入循环依赖
java
@Component
public class OrderService {
private final UserService userService;
@Autowired
public OrderService(UserService userService) {
this.userService = userService;
}
}
报错信息:
vbnet
BeanCurrentlyInCreationException: Error creating bean with name 'orderService':
Requested bean is currently in creation: Is there an unresolvable circular reference?
原因: 构造器注入发生在实例化阶段,此时对象还未创建,无法提前暴露引用。
解决方案:
java
// 方案一:改用@Setter注入或字段注入
@Component
public class OrderService {
@Autowired
private UserService userService;
}
// 方案二:使用@Lazy注解(推荐)
@Component
public class OrderService {
private final UserService userService;
@Autowired
public OrderService(@Lazy UserService userService) {
this.userService = userService;
}
}
// 方案三:重构代码,消除循环依赖(最佳实践)
问题2:@Async导致的循环依赖问题
java
@Component
public class OrderService {
@Autowired
private UserService userService;
@Async
public void processOrder() {
// ...
}
}
在某些Spring版本中,@Async会引入AOP代理,可能导致循环依赖处理异常。
解决方案: 在启动类上添加@EnableAsync,并确保异步配置正确,或使用@Lazy注解。
问题3:Spring Boot 2.6+默认禁止循环依赖
从Spring Boot 2.6开始,默认禁止循环依赖。
报错信息:
markdown
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| orderService (field private com.example.UserService com.example.OrderService.userService)
↑ ↓
| userService (field private com.example.OrderService com.example.UserService.orderService)
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default.
Update your application to remove the dependency cycle between beans.
解决方案:
properties
# 方案一:在application.properties中允许循环依赖
spring.main.allow-circular-references=true
java
// 方案二:通过代码设置
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.setAllowCircularReferences(true);
app.run(args);
}
}
问题4:prototype作用域的循环依赖
Spring不支持prototype作用域的循环依赖。因为prototype的Bean不会被缓存,每次获取都是新创建的。
解决方案: 使用ObjectProvider或Provider延迟获取。
java
@Component
public class OrderService {
@Autowired
private ObjectProvider<UserService> userServiceProvider;
public void doSomething() {
UserService userService = userServiceProvider.getObject();
}
}
问题5:如何主动关闭循环依赖
java
// 方式一:通过API关闭
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.setAllowCircularReferences(false);
ac.register(AppConfig.class);
ac.refresh();
// 方式二:Spring Boot配置
spring.main.allow-circular-references=false
八、最佳实践
虽然Spring提供了循环依赖的支持,但在实际开发中,循环依赖通常意味着架构设计存在问题。
推荐的解决方式(按优先级排序):
-
重构代码,消除循环依赖(最佳实践)
- 提取公共逻辑到第三个Bean中
- 使用事件驱动(
ApplicationEvent)解耦 - 使用观察者模式
-
使用
@Lazy注解- 延迟加载,避免启动时报错
- 适合临时方案
-
使用
ObjectProvider/Provider- 延迟获取依赖
- 更加灵活
-
使用
@PostConstruct- 在初始化阶段手动注入
使用事件驱动解耦示例:
java
@Component
public class OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void createOrder() {
// 创建订单逻辑...
// 发布事件,而非直接调用UserService
eventPublisher.publishEvent(new OrderCreatedEvent(this, orderId));
}
}
@Component
public class UserService {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
// 处理订单创建后的用户相关逻辑
}
}
九、总结
| 知识点 | 说明 |
|---|---|
| 循环依赖 | 两个或多个Bean互相持有对方引用 |
| 前提条件 | 单例模式 + 属性注入(非构造器) |
| 核心机制 | 三级缓存 |
| 一级缓存 | singletonObjects,存放完整Bean |
| 二级缓存 | earlySingletonObjects,存放早期Bean引用 |
| 三级缓存 | singletonFactories,存放ObjectFactory |
| 为什么用三级 | 为了支持AOP代理场景 |
| Spring Boot 2.6+ | 默认禁止循环依赖 |
| 最佳实践 | 重构消除循环依赖 > @Lazy > ObjectProvider |
理解Spring的循环依赖解决机制,本质上就是理解Spring的三级缓存设计思想。核心在于提前暴露------在Bean还未完全初始化时,就将其早期引用暴露给其他Bean使用,从而打破循环等待的死锁。
希望这篇文章能帮助你彻底理解Spring循环依赖的原理和解决方案。如果有任何疑问,欢迎在评论区交流讨论!
关注我,持续分享Java技术干货。觉得有帮助的话,点个赞再走吧!