知识模块
☕ Java 知识模块
八、Spring 全家桶
Spring 循环依赖

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 如何解决循环依赖?

回答要点:

  1. 通过三级缓存
  2. 一级缓存:完整的 Bean
  3. 二级缓存:早期的 Bean(未完成属性注入)
  4. 三级缓存:Bean 工厂(延迟创建代理)
  5. 创建 Bean 时先暴露到三级缓存,循环依赖时提升到二级缓存

Q2: 为什么需要三级缓存而不是两级?

回答要点:

  1. 三级缓存存储 ObjectFactory,延迟创建代理
  2. 只有发生循环依赖时才创建代理
  3. 避免所有 Bean 都提前创建代理
  4. 确保注入的是代理对象

Q3: 哪些循环依赖无法解决?

  1. 构造器注入(可用 @Lazy 解决)
  2. Prototype 作用域
  3. @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. 三级缓存的优势:延迟创建代理