Spring 循环依赖
什么是循环依赖?
@Service
public class A {
@Autowired
private B b;
}
@Service
public class B {
@Autowired
private A a;
}
// A 依赖 B,B 依赖 A,形成循环循环依赖场景
| 场景 | 是否能解决 |
|---|---|
| 构造器注入 + 构造器注入 | ❌ 无法解决 |
| Setter 注入 + Setter 注入 | ✅ 可以解决 |
| 构造器注入 + Setter 注入 | ❌ 无法解决 |
| Prototype 作用域 | ❌ 无法解决 |
三级缓存解决方案
三级缓存结构
public class DefaultSingletonBeanRegistry {
// 一级缓存:完整的 Bean(已初始化)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:早期 Bean(已实例化,未初始化)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存:Bean 工厂(用于生成早期引用或代理)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 已注册的 Bean 名称
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
// 正在创建的 Bean
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}解决流程
创建 A:
1. 实例化 A(调用构造函数)
2. 暴露 A 到三级缓存(singletonFactories)
3. 注入 B → 发现 B 不存在
创建 B:
4. 实例化 B
5. 暴露 B 到三级缓存
6. 注入 A → 从三级缓存获取 A
- 调用 ObjectFactory.getObject()
- 如果 A 需要 AOP,返回代理对象
- 将 A 从三级缓存移到二级缓存
7. B 完成初始化,放入一级缓存
回到创建 A:
8. 获取 B(从一级缓存)
9. A 完成初始化,放入一级缓存流程图
┌──────────────────────────────────────────────────────────────┐
│ 创建 Bean A │
└──────────────────────────────────────────────────────────────┘
│
▼ 实例化 A
┌─────────────────┐
│ A 实例化完成 │
│ (未初始化) │
└─────────────────┘
│
▼ 暴露到三级缓存
┌─────────────────────────────────────────┐
│ singletonFactories.put("a", () -> getEarlyBeanReference(a)) │
└─────────────────────────────────────────┘
│
▼ 注入 B
┌──────────────────────────────────────────────────────────────┐
│ 创建 Bean B │
└──────────────────────────────────────────────────────────────┘
│
▼ 实例化 B
▼ 暴露到三级缓存
▼ 注入 A
┌─────────────────────────────────────────┐
│ 从三级缓存获取 A: │
│ ObjectFactory.getObject() │
│ ↓ │
│ 返回 A 或 A 的代理 │
│ ↓ │
│ 移到二级缓存: │
│ earlySingletonObjects.put("a", A) │
│ singletonFactories.remove("a") │
└─────────────────────────────────────────┘
│
▼ B 初始化完成
┌─────────────────────────────────────────┐
│ singletonObjects.put("b", B) │
└─────────────────────────────────────────┘
│
▼ 回到创建 A
┌─────────────────────────────────────────┐
│ 获取 B(从一级缓存) │
│ A 初始化完成 │
│ singletonObjects.put("a", A) │
└─────────────────────────────────────────┘为什么需要三级缓存?
如果只有两级缓存
// 假设只有一级和二级缓存
// 问题:何时创建代理对象?
// 方案一:实例化时就创建代理
// 缺点:所有 Bean 都提前创建代理,浪费资源
// 方案二:初始化后创建代理
// 缺点:循环依赖时,注入的是原始对象而非代理三级缓存的优势
// 三级缓存存储的是 ObjectFactory
singletonFactories.put(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// getEarlyBeanReference 方法
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
// 只有在需要时才创建代理
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}关键点:
- 只有发生循环依赖时,才会调用
getEarlyBeanReference - 代理对象延迟创建,避免浪费
- 确保注入的是代理对象而非原始对象
构造器注入为何无法解决?
@Service
public class A {
private final B b;
public A(B b) {
this.b = b; // 构造时就需要 B
}
}
@Service
public class B {
private final A a;
public B(A a) {
this.a = a; // 构造时就需要 A
}
}
// 问题:
// 创建 A → 需要 B → B 未创建
// 创建 B → 需要 A → A 未创建完成(无法暴露到缓存)
// 死锁!解决方案
// 方案一:使用 @Lazy
@Service
public class A {
private final B b;
public A(@Lazy B b) {
this.b = b; // 注入代理,延迟加载
}
}
// 方案二:改用 Setter 注入
@Service
public class A {
@Autowired
private B b;
}Prototype 为何无法解决?
@Service
@Scope("prototype")
public class A {
@Autowired
private B b;
}
@Service
@Scope("prototype")
public class B {
@Autowired
private A a;
}
// 原因:Prototype Bean 不使用缓存
// 每次获取都创建新实例,无法复用代码示例
Setter 注入(可解决)
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
public void doA() {
System.out.println("ServiceA");
}
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
public void doB() {
System.out.println("ServiceB");
}
}
// Spring 通过三级缓存自动解决构造器注入 + @Lazy(解决)
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
// @Lazy 让 ServiceB 延迟初始化面试高频问题
Q1: Spring 如何解决循环依赖?
回答要点:
- 通过三级缓存
- 一级缓存:完整的 Bean
- 二级缓存:早期的 Bean(未完成属性注入)
- 三级缓存:Bean 工厂(延迟创建代理)
- 创建 Bean 时先暴露到三级缓存,循环依赖时提升到二级缓存
Q2: 为什么需要三级缓存而不是两级?
回答要点:
- 三级缓存存储 ObjectFactory,延迟创建代理
- 只有发生循环依赖时才创建代理
- 避免所有 Bean 都提前创建代理
- 确保注入的是代理对象
Q3: 哪些循环依赖无法解决?
- 构造器注入(可用 @Lazy 解决)
- Prototype 作用域
- @Async 导致的循环依赖(代理创建时机问题)
Q4: 如何检测循环依赖?
// Spring 通过 singletonsCurrentlyInCreation 集合检测
// 如果创建 Bean 时发现已在创建中,说明存在循环依赖
if (isSingletonCurrentlyInCreation(beanName)) {
// 检查二级缓存
Object earlySingletonObject = earlySingletonObjects.get(beanName);
if (earlySingletonObject == null) {
// 检查三级缓存
ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
if (singletonFactory != null) {
earlySingletonObject = singletonFactory.getObject();
earlySingletonObjects.put(beanName, earlySingletonObject);
singletonFactories.remove(beanName);
}
}
return earlySingletonObject;
}总结
循环依赖核心要点:
1. Setter 注入可通过三级缓存解决
2. 三级缓存:完整 Bean、早期 Bean、Bean 工厂
3. 构造器注入无法解决(用 @Lazy)
4. Prototype 无法解决
5. 三级缓存的优势:延迟创建代理