Spring循环依赖与三级缓存

Spring循环依赖与三级缓存

循环依赖是 Spring 应用中常见的问题。理解 Spring 如何解决循环依赖,有助于排查 Bean 创建异常和设计更好的代码。

什么是循环依赖

@Component
public class A {
@Autowired
private B b; // A 依赖 B
}

@Component
public class B {
@Autowired
private A a; // B 依赖 A
}
创建 A -> 需要 B
-> 创建 B -> 需要 A
-> 创建 A -> 循环!

Spring 如何解决

Spring 通过三级缓存解决 setter 注入的循环依赖。

三级缓存结构

public class DefaultSingletonBeanRegistry {
// 一级缓存:完整的单例 Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 二级缓存:提前暴露的 Bean(未填充属性)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

// 三级缓存:Bean 的工厂(用于生成代理对象)
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) {
// 1. 从一级缓存获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 2. 从二级缓存获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 3. 从三级缓存获取
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; // setter/field 注入
}

方案2:使用 @Lazy

@Component
public class A {
private final B b;

public A(@Lazy B b) { // 延迟注入
this.b = b;
}
}

原理:注入的是 B 的代理对象,延迟到真正使用时才创建。

方案3:重新设计(推荐)

// 提取公共逻辑到 C
@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;
// 如果配置了 SmartInstantiationAwareBeanPostProcessor(如AOP)
// 返回代理对象
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

三级缓存的设计精妙之处:

  1. 一级:保证单例唯一
  2. 二级:支持提前暴露
  3. 三级:支持代理对象的提前暴露

理解循环依赖的解决机制,有助于写出更健壮的 Spring 代码。


   转载规则


《Spring循环依赖与三级缓存》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录