Spring循环依赖与三级缓存
循环依赖是 Spring 应用中常见的问题。理解 Spring 如何解决循环依赖,有助于排查 Bean 创建异常和设计更好的代码。
什么是循环依赖
@Component public class A { @Autowired private B b; }
@Component public class B { @Autowired private A a; }
|
创建 A -> 需要 B -> 创建 B -> 需要 A -> 创建 A -> 循环!
|
Spring 如何解决
Spring 通过三级缓存解决 setter 注入的循环依赖。
三级缓存结构
public class DefaultSingletonBeanRegistry { private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); }
|
解决流程
1. getBean(A) ├── 标记 A 正在创建 ├── 实例化 A(调用构造器) ├── 将 A 的 ObjectFactory 放入三级缓存 ├── 填充 A 的属性 -> 发现需要 B │ ├── getBean(B) │ ├── 标记 B 正在创建 │ ├── 实例化 B │ ├── 将 B 的 ObjectFactory 放入三级缓存 │ ├── 填充 B 的属性 -> 发现需要 A │ │ │ ├── getBean(A) │ │ ├── 从三级缓存获取 A 的 ObjectFactory │ │ ├── 执行 getObject() -> 获取 A 的早期引用 │ │ └── 将 A 放入二级缓存,从三级缓存删除 │ │ │ ├── B 的属性填充完成(A 的早期引用) │ ├── B 初始化完成 │ └── 将 B 放入一级缓存 │ ├── A 的属性填充完成(B 的完整引用) ├── A 初始化完成 └── 将 A 放入一级缓存
|
关键代码
protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } return singletonObject; }
|
构造器循环依赖无法解决
@Component public class A { private final B b; public A(B b) { this.b = b; } }
@Component public class B { private final A a; public B(A a) { this.a = a; } }
|
原因:
- 构造器注入时,对象必须先完全创建才能暴露
- 但 A 创建需要 B,B 创建需要 A
- 无法像 setter 注入那样先暴露早期引用
错误:
BeanCurrentlyInCreationException: Error creating bean with name 'A': Requested bean is currently in creation
|
解决方案
方案1:改为 Setter 注入
@Component public class A { @Autowired private B b; }
|
方案2:使用 @Lazy
@Component public class A { private final B b; public A(@Lazy B b) { this.b = b; } }
|
原理:注入的是 B 的代理对象,延迟到真正使用时才创建。
方案3:重新设计(推荐)
@Component public class C { }
@Component public class A { @Autowired private C c; }
@Component public class B { @Autowired private C c; }
|
三级缓存的设计原因
为什么需要三级?
只用一级缓存:
- 问题:循环依赖时,A 还没创建完就被 B 引用,一级缓存中是完整的 Bean
- 解决:需要提前暴露未完成的 Bean
只用二级缓存:
- 问题:如果 A 需要代理,提前暴露的应该是代理对象
- 二级缓存直接存对象,无法处理代理
三级缓存:
- 三级存
ObjectFactory,可以决定暴露原始对象还是代理对象
AbstractAutowireCapableBeanFactory.getEarlyBeanReference() 处理代理
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }
|
多例循环依赖
@Scope("prototype") @Component public class A { @Autowired private B b; }
@Scope("prototype") @Component public class B { @Autowired private A a; }
|
问题:
- 多例 Bean 不缓存,每次创建都是新的
- 每次 getBean(A) 都要创建新的 A,创建 A 需要新的 B,创建 B 需要新的 A
- 无限循环
解决:
- 避免多例 Bean 的循环依赖
- 或使用 @Lazy
总结
| 场景 |
能否解决 |
方案 |
| 单例 setter 循环依赖 |
可以 |
三级缓存 |
| 单例构造器循环依赖 |
不可以 |
改 setter / @Lazy / 重构 |
| 多例循环依赖 |
不可以 |
避免 / @Lazy |
三级缓存的设计精妙之处:
- 一级:保证单例唯一
- 二级:支持提前暴露
- 三级:支持代理对象的提前暴露
理解循环依赖的解决机制,有助于写出更健壮的 Spring 代码。