Spring循环依赖的终极解密:三级缓存如何拯救Bean创建死锁
引子:一场让人头疼的"鸡生蛋"困局
还记得那个让所有Java新手抓狂的问题吗?UserService依赖OrderService,OrderService又依赖UserService,就像两个人互相等对方先开口说话一样尴尬。
那天下午,小李兴冲冲地写完代码,准备展示他的"完美设计":
java
@Service
public class UserService {
@Autowired
private OrderService orderService;
// ...业务逻辑
}
@Service
public class OrderService {
@Autowired
private UserService userService;
// ...业务逻辑
}
结果启动项目时,Spring直接给了他一个"循环依赖"的大红叉。就像两个人同时伸手要握手,结果撞在了一起。
探索:Spring容器的"智慧"登场
好奇心驱使下,我们开始探索Spring是如何解决这个看似无解的问题的。原来,Spring容器就像一个聪明的调解员,它设计了一套"三级缓存"机制。
想象一下,这就像一个三层的仓库系统:
缓存级别 | 名称 | 作用 |
---|---|---|
一级缓存 | singletonObjects | 存放完全初始化好的Bean |
二级缓存 | earlySingletonObjects | 存放提前暴露的Bean |
三级缓存 | singletonFactories | 存放Bean工厂 |
转折:深入缓存背后的"魔法"
刚开始我以为很简单,不就是分层存储吗?直到我看到Spring源码中的关键方法:
java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// ...缓存转移逻辑
}
}
}
return singletonObject;
}
这里的逻辑简直像侦探小说一样精妙!Spring会依次从三个缓存中查找Bean,就像在三个不同的抽屉里找钥匙。
解决:循环依赖的"时空穿越"术
关键时刻到了!Spring解决循环依赖的核心思路是:提前暴露未完成的Bean实例。
想象这样的场景:
- 创建UserService:Spring先创建一个"半成品"的UserService实例
- 提前暴露:把这个半成品放入三级缓存
- 创建OrderService:需要UserService时,从缓存中拿到那个半成品
- 完善注入:OrderService创建完成后,回过头完善UserService
java
// Spring内部的Bean创建流程(简化版)
public Object createBean(String beanName) {
// 1. 创建Bean实例(构造函数调用)
Object bean = instantiateBean(beanName);
// 2. 提前暴露到三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, bean));
// 3. 属性注入(这里可能触发循环依赖)
populateBean(bean);
// 4. 初始化完成
return initializeBean(bean);
}
这就像两个工匠同时造房子,A工匠先把地基打好(创建实例),然后告诉B工匠"我的地址在这里"(提前暴露),B工匠就可以先连接管道了(依赖注入)。
踩坑瞬间:不是所有循环依赖都能救
然而,Spring的三级缓存并非万能药。我曾天真地以为所有循环依赖都能解决,直到遇到了构造函数循环依赖:
java
@Service
public class UserService {
private final OrderService orderService;
public UserService(OrderService orderService) { // 构造函数注入
this.orderService = orderService;
}
}
这种情况下,Spring直接投降了!因为构造函数必须在Bean实例化时就执行,无法"提前暴露"一个还没创建的对象。
就像你不能在房子还没开始建的时候,就告诉别人房子的地址一样。
经验启示:设计模式中的智慧
通过这次深入了解,我发现了几个宝贵经验:
1. 优先使用字段注入或Setter注入
java
@Autowired // 字段注入,支持循环依赖
private OrderService orderService;
2. 合理设计依赖关系
- 避免强耦合的双向依赖
- 考虑使用事件驱动或中介者模式
- 适当时候引入接口抽象
3. 理解Spring的生命周期
Spring的Bean创建就像一场精心编排的舞蹈,每一步都有它的节拍和意义。
总结:化解困局的艺术
Spring的三级缓存机制,本质上是一种"延迟满足"的设计哲学。它告诉我们,有时候解决问题不是硬碰硬,而是巧妙地利用时间差和空间差。 就像人生中的很多困境一样,看似无解的循环往往可以通过改变思路和时机来化解。Spring用三级缓存教会了我们:真正的智慧不在于避免所有问题,而在于优雅地解决看似无解的问题。 下次再遇到循环依赖时,你就知道Spring容器正在幕后默默地为你运行这套精妙的"时空穿越"算法了!
本文转自渣哥zha-ge.cn 本文转自渣哥zha-ge.cn