前言
在 Spring 中,循环依赖(Circular Dependency)是指两个或多个 Bean 互相依赖,导致 Spring 无法正确实例化它们。例如,以下代码展示了一个简单的循环依赖场景:
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
在 A 需要 B,同时 B 也需要 A 时,Spring 如何解决这个循环依赖呢?这就涉及到 Spring 的 三级缓存机制。
Spring 三级缓存
Spring 为了解决 单例 Bean 的循环依赖,采用了 三级缓存机制,主要涉及以下三个 Map:
缓存级别 | 作用 |
---|---|
一级缓存(singletonObjects) | 存放已经创建完毕、可以正常使用的 Bean。 |
二级缓存(earlySingletonObjects | 存放刚刚创建但还没初始化完成的 Bean,加速后续获取。 |
三级缓存(singletonFactories | 存放 Bean 的“生产工厂”,当需要时可以创建 Bean(支持 AOP 代理) |
Spring 如何解决循环依赖
步骤 | 作用 |
---|---|
实例化对象 | 创建 new A() 和 new B(),但未赋值 |
提前曝光 | A 进入进入三级缓存,避免重复创建 |
依赖注入 | 发现 B 需要 A,从三级缓存获取 A 并放入二级缓存 |
初始化 | B先初始化,然后A也完成 |
放入一级缓存 | A 和 B 都完成创建,进入一级缓存,可以正常使用 |
- 实例化 Bean:创建对象(new 一个实例)。
- 提前曝光:把 Bean 的“创建工厂”存入三级缓存,方便后续获取。
- 依赖注入:给 Bean 赋值,把它需要的其他 Bean 填充进去。
- 初始化。
- 放入一级缓存:表示 Bean 完全准备好,可以正常使用了。
三级缓存如何工作
以 A 和 B 互相依赖的情况为例,Spring 在创建 A 时,会经历以下步骤:
第一步会先查询A对象是否在三级缓存中是否已有 A,如果找到就直接返回,避免重复创建
实例化 A:如果缓存中没有 A,Spring 调用 new A() 创建对象,此时 A 还未填充 B。
此时需要填充a对象的属性,也是就是b对象
这是也会查询B对象是否在缓存中
实例化 B:如果缓存中没有 B,Spring 调用 new B() 创建对象,此时 B 还未填充 A。
下一步就是填充对象b对象的属性,也就是创建a
此时需要在去缓存查是否有a对象,这里发现有三级缓存里面有a和b对象了,此时就要做一些额外的操作
// 首先从三级缓存拿到a
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 得到真正的对象a
singletonObject = singletonFactory.getObject();
// 将得到的a对象放入二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
// 把a对象在三级缓存中移除
this.singletonFactories.remove(beanName);
}
此时把B对象的A对象属性进行填充,之后再进行初始化
完成 B 的创建,并将其放入 一级缓存(singletonObjects)。进行缓存转移
// 关键源码
protected void addSingleton(String beanName, Object singletonObject) {
synchronized(this.singletonObjects) {
// 把完整的b对象方法放一级缓存里面
this.singletonObjects.put(beanName, singletonObject);
// 三级缓存中移除到b对象
this.singletonFactories.remove(beanName);
// 再把二级缓存移除掉,这里显然二级缓存没有b对象
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
到这里整个b对象的创建过程已经完成了
回到开头,b对象的创建的过程一直在a对象的填充属性里面,之后进行填充。完成 a 的创建,并将其放入 一级缓存(singletonObjects)。进行缓存转移
// 关键源码
protected void addSingleton(String beanName, Object singletonObject) {
synchronized(this.singletonObjects) {
// 把完整的a对象方法放一级缓存里面
this.singletonObjects.put(beanName, singletonObject);
// 三级缓存移除a对象,如果有的话移除
this.singletonFactories.remove(beanName);
// 再把二级缓存移除掉a对象
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
到这里整个a对象的创建就完成了,最终 A 和 B 都能正常使用。
三级缓存为什么可以解决循环依赖?
如果没有三级缓存,Spring 需要先完全创建 A,然后再创建 B,导致无限递归。但有了三级缓存后:
- A 可以在未完全初始化时,就被 B 访问,避免重复创建。
- B 也可以顺利创建,不会卡在 A 未完成的问题上。
为什么一定要三级缓存
在 Spring 解决 循环依赖 时,三级缓存的核心作用是确保 AOP 代理能够正确生效。
如果 Spring 直接把 A 存入缓存,而 A 只是一个普通对象,没有经过 AOP 代理处理,那么 B 依赖 A 时,拿到的还是未增强的 A。之后即使 A 经过 AOP 代理增强,B 仍然持有原始的 A,导致 事务、日志等 AOP 功能失效。
三级缓存中的工厂方法(SingletonFactories)并不意味着每个 Bean 都会有代理对象。它只是一个延迟初始化的机制,当某个 Bean 需要 AOP 功能时,才会通过这个工厂方法来创建代理对象。
如果 Bean 不需要 AOP 增强,Spring 就不会创建代理对象,也就不会涉及三级缓存中的代理工厂。
二级缓存为什么不能解决问题?
如果 Spring 只使用二级缓存,流程如下:
- A 创建后,直接放入二级缓存(earlySingletonObjects)。
- B 依赖 A,直接从二级缓存获取 A。
- B 创建完成,存入一级缓存。
- A 继续完成初始化,此时才应用 AOP 代理,但 B 早已持有的是原始 A,代理不生效。
问题:
- B 依赖的 A 是未代理的原始对象,无法享受 AOP 增强(例如事务、日志)。
- 即使 A 之后被代理,B 仍然持有的是原始 A,AOP 依然失效。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。