Skip to content

Spring 三级缓存与循环依赖

一、循环依赖概述

什么是循环依赖

java
// 场景1:构造器循环依赖
@Service
public class A {
    public A(B b) { }  // A → B
}

@Service
public class B {
    public B(A a) { }  // B → A
}

// 场景2:Setter 循环依赖
@Service
public class A {
    @Autowired
    private B b;
}

@Service
public class B {
    @Autowired
    private A a;
}

// 场景3:自身循环依赖
@Service
public class A {
    @Autowired
    private A self;
}

依赖图示

┌─────────────────────────────────────────────────────────────────┐
│                    循环依赖示意                                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  构造器循环依赖(无法解决 ❌)                                      │
│                                                                  │
│      ┌─────┐                                                    │
│      │  A  │                                                    │
│      │  │  │                                                    │
│      │  ▼  │                                                    │
│      │  B  │                                                    │
│      │  │  │                                                    │
│      └──┬──┘                                                    │
│         │                                                       │
│         └──────────────────────────────────────┘                │
│                                                                  │
│  Setter 循环依赖(可以解决 ✅)                                    │
│                                                                  │
│      ┌─────┐     ┌─────┐                                       │
│      │  A  │────▶│  B  │                                       │
│      │     │◀────│     │                                       │
│      └─────┘     └─────┘                                       │
│        │           │                                           │
│        └───────────┘                                           │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

二、三级缓存详解

缓存结构

┌─────────────────────────────────────────────────────────────────┐
│                    Spring 三级缓存                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                    第一级缓存 (singletonObjects)           │  │
│  │  ┌─────────────────────────────────────────────────────┐   │  │
│  │  │  Map<String, Object>                              │   │  │
│  │  │  完全初始化好的 Bean                                │   │  │
│  │  │  可以直接使用                                      │   │  │
│  │  └─────────────────────────────────────────────────────┘   │  │
│  └───────────────────────────────────────────────────────────┘  │
│                                                                  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                    第二级缓存 (earlySingletonObjects)       │  │
│  │  ┌─────────────────────────────────────────────────────┐   │  │
│  │  │  Map<String, Object>                              │   │  │
│  │  │  早期暴露的 Bean(未完成属性填充和方法调用)          │   │  │
│  │  │  仅完成实例化 + 属性填充 + init 调用                │   │  │
│  │  └─────────────────────────────────────────────────────┘   │  │
│  └───────────────────────────────────────────────────────────┘  │
│                                                                  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                    第三级缓存 (singletonFactories)          │  │
│  │  ┌─────────────────────────────────────────────────────┐   │  │
│  │  │  Map<String, ObjectFactory<?>>                     │   │  │
│  │  │  Bean 工厂,用于创建早期引用                        │   │  │
│  │  │  getObject() 返回早期代理对象                       │   │  │
│  │  └─────────────────────────────────────────────────────┘   │  │
│  └───────────────────────────────────────────────────────────┘  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

源码位置

java
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
public class DefaultSingletonBeanRegistry extends FactoryBeanRegistrySupport {
    
    // 第一级缓存:完全初始化好的 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);
}

三、解决流程

正常 Bean 创建流程

┌─────────────────────────────────────────────────────────────────┐
│                    Bean 创建完整流程                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. createBeanInstance()                                        │
│     └─▶ 调用构造函数创建实例                                       │
│     └─▶ 放入第三级缓存 singletonFactories                        │
│                                                                  │
│  2. populateBean()                                              │
│     └─▶ 属性填充(依赖注入)                                      │
│     └─▶ 处理 @Autowired、@Value 等                              │
│                                                                  │
│  3. initializeBean()                                            │
│     └─▶ 调用 init 方法                                           │
│     └─▶ BeanPostProcessor.postProcessAfterInitialization       │
│                                                                  │
│  4. 放入第一级缓存                                               │
│     └─▶ 从二、三级缓存移除                                       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

循环依赖解决流程

┌─────────────────────────────────────────────────────────────────┐
│                 循环依赖解决详细流程                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  场景:A → B → A 循环依赖                                        │
│                                                                  │
│  Step 1: 创建 A                                                 │
│  ├─ createBeanInstance() → A 实例                                │
│  ├─ 放入第三级缓存 singletonFactories                           │
│  │   └─ A 的 ObjectFactory: () -> getEarlyBeanReference(A)     │
│  │                                                             │
│  └─ populateBean() 发现依赖 B                                   │
│                                                                  │
│  Step 2: 创建 B                                                 │
│  ├─ createBeanInstance() → B 实例                               │
│  ├─ 放入第三级缓存 singletonFactories                          │
│  │                                                             │
│  └─ populateBean() 发现依赖 A                                   │
│                                                                  │
│  Step 3: 获取 A 的早期引用                                       │
│  ├─ 先查第一级缓存 → 没有                                       │
│  ├─ 再查第二级缓存 → 没有                                       │
│  ├─ 再查第三级缓存 → 有!                                       │
│  │   └─ 调用 getObject() 获取 A 的早期引用(可能已被代理)       │
│  ├─ 放入第二级缓存 earlySingletonObjects                       │
│  └─ 从第三级缓存移除                                             │
│                                                                  │
│  Step 4: B 完成创建                                             │
│  ├─ populateBean() 继续                                        │
│  ├─ initializeBean()                                           │
│  ├─ 放入第一级缓存                                              │
│  └─ 从二、三级缓存移除                                          │
│                                                                  │
│  Step 5: 回到 A 的创建                                          │
│  ├─ 获得 B 的完整引用                                           │
│  ├─ populateBean() 继续                                        │
│  ├─ initializeBean()                                           │
│  └─ 放入第一级缓存                                              │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

核心代码

java
// getSingleton() 方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 先查第一级缓存
    Object singleton = this.singletonObjects.get(beanName);
    if (singleton == null && isSingletonCurrentlyInCreation(beanName)) {
        // 2. 再查第二级缓存(需要加锁)
        synchronized (this.earlySingletonObjects) {
            singleton = this.earlySingletonObjects.get(beanName);
            if (singleton == null && allowEarlyReference) {
                // 3. 再查第三级缓存
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 获取早期引用
                    singleton = singletonFactory.getObject();
                    // 放入第二级缓存
                    this.earlySingletonObjects.put(beanName, singleton);
                    // 移除第三级缓存
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singleton;
}

四、为什么需要第三级缓存

不用第三级缓存可以吗?

java
// 假设只有两级缓存
// 问题:如何保证返回的是正确的代理对象?

第三级缓存的作用

┌─────────────────────────────────────────────────────────────────┐
│                    第三级缓存存在的意义                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  目的:延迟代理对象的创建                                         │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                         │    │
│  │  如果直接创建代理对象:                                   │    │
│  │  - 时机太早(Bean 可能不需要代理)                      │    │
│  │  - 可能被多次重复创建                                   │    │
│  │                                                         │    │
│  │  第三级缓存的作用:                                     │    │
│  │  - 存储 ObjectFactory,延迟获取早期引用                 │    │
│  │  - 只有真正需要时(如循环依赖)才调用                   │    │
│  │  - 保证每个 Bean 只创建一次早期引用                     │    │
│  │                                                         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
│  流程:                                                         │
│  1. 放入 ObjectFactory                                          │
│  2. 需要时调用 getObject()                                      │
│  3. 获取后放入第二级缓存,移除第三级                             │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

完整流程图

                              ┌─────────────────┐
                              │   创建 Bean A    │
                              └────────┬────────┘

                    ┌──────────────────┼──────────────────┐
                    ▼                  ▼                  ▼
           ┌────────────┐     ┌────────────┐     ┌────────────┐
           │  第一级     │     │  第二级     │     │  第三级     │
           │ singleton  │     │ earlySingle │     │ singleFact │
           │  Objects   │     │   Objects   │     │   -ories   │
           └─────┬──────┘     └─────┬──────┘     └─────┬──────┘
                 │                  │                  │
                 │                  │                  │
    A创建完成 ◀───┼──────────────────┼──────────────────┼
                 │                  │                  │
                 │                  │                  │
    populate(A) ◀─┼──────────────────┼──────────────────┼
        发现依赖B  │                  │                  │
                 │                  │                  │
        ┌─────────┴─────────┐       │                  │
        ▼                   ▼       ▼                  ▼
   ┌────────┐         ┌────────┐ ┌────────┐      ┌────────┐
   │        │         │        │ │        │      │        │
   │  没有  │         │  没有  │ │ 没有   │      │ 有     │
   │        │         │        │ │        │      │Object  │
   │        │         │        │ │        │      │Factory │
   └────────┘         └────────┘ └────────┘      └────┬───┘

                                          getEarlyBeanReference()

                                          调用ObjectFactory.getObject()


                                         ┌─────────────────────┐
                                         │  获取A的早期引用    │
                                         │  (可能返回代理对象)  │
                                         └──────────┬──────────┘

                                        放入二级缓存,移除三级缓存


                                         ┌─────────────────────┐
                                         │      A早期引用      │
                                         └──────────┬──────────┘

                                    ┌────────────────┼────────────────┐
                                    ▼                ▼                ▼
                              ┌────────┐       ┌────────┐       ┌────────┐
                              │ 第一级  │       │ 第二级  │       │ 第三级  │
                              │   有   │       │   有   │       │   无   │
                              └────────┘       └────────┘       └────────┘

五、构造器循环依赖为什么不能解决

┌─────────────────────────────────────────────────────────────────┐
│                    构造器循环依赖无法解决                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  原因:构造器执行时机在 populateBean 之前                        │
│                                                                  │
│  A 构造器需要 B → 需要先创建 B                                   │
│  B 构造器需要 A → 需要先创建 A                                   │
│  ...无限循环                                                     │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                         │    │
│  │  createBeanInstance() → 构造器                         │    │
│  │       │                                                │    │
│  │       ▼                                                │    │
│  │  populateBean() → 属性填充                            │    │
│  │       │                                                │    │
│  │       ▼                                                │    │
│  │  initializeBean() → 初始化                           │    │
│  │                                                         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
│  构造器阶段无法获取早期引用,因为 Bean 还未创建完成                │
│                                                                  │
│  解决方案:                                                      │
│  1. 改用 Setter 注入                                           │
│  2. 使用 @Lazy 延迟加载                                        │
│  3. 使用 Provider 延迟引用                                      │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

@Lazy 解决

java
@Service
public class A {
    public A(@Lazy B b) { }  // 延迟注入
}

六、prototype 循环依赖

┌─────────────────────────────────────────────────────────────────┐
│                    prototype 循环依赖无法解决                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  原因:prototype Bean 不缓存,每次获取都创建新实例                │
│                                                                  │
│  Spring 只缓存 singleton 的 Bean                                 │
│  prototype Bean 每次都走完整创建流程                              │
│  无法通过缓存获取早期引用                                         │
│                                                                  │
│  解决方案:                                                      │
│  1. 改用 singleton                                              │
│  2. 使用 ObjectFactory / Provider                               │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

七、@Async 循环依赖问题

┌─────────────────────────────────────────────────────────────────┐
│                    @Async 循环依赖问题                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  问题:@Async 会生成代理对象,但三级缓存存的是 ObjectFactory      │
│                                                                  │
│  @Async 代理创建时机:                                          │
│  - AsyncAutoConfiguration                                     │
│  - BeanPostProcessor.postProcessAfterInitialization           │
│                                                                  │
│  如果在 populateBean 时发现循环依赖,调用 ObjectFactory.getObject()
│  此时 @Async 代理可能还未创建                                    │
│                                                                  │
│  解决方案:                                                      │
│  spring.main.allow-circular-references=false                   │
│  或升级 Spring 版本                                              │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

八、面试高频问题

Q1: 什么是循环依赖?

两个或多个 Bean 相互依赖,形成闭环。如 A→B→A

Q2: Spring 三级缓存作用?

  • 一级:完全初始化好的 Bean
  • 二级:早期暴露的 Bean(未完成初始化)
  • 三级:Bean 工厂,延迟创建早期引用

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

  1. 构造器循环依赖
  2. prototype Bean 的循环依赖
  3. @Async 代理的循环依赖

Q4: 为什么需要第三级缓存?

延迟代理对象的创建,保证在真正需要时才创建早期引用

Q5: 如何解决循环依赖?

  1. 改用 setter 注入
  2. 使用 @Lazy 延迟加载
  3. 使用 @Autowired(required = false) 可选依赖

九、下一章预告

下一章我们将学习 Spring 总结与实战

  • 全家桶核心概念回顾
  • 企业级应用架构设计
  • 常用配置汇总

基于 MIT 许可发布