Interview AiBox logo

Interview AiBox 实时 AI 助手,让你自信应答每一场面试

download免费下载
高阶local_fire_department8 次面试更新于 2025-09-03account_tree思维导图

请解释Spring框架中的三级缓存机制及其在解决循环依赖问题中的作用。

lightbulb

题型摘要

Spring框架中的三级缓存机制是解决循环依赖问题的核心设计。它包括三个Map缓存:一级缓存(singletonObjects)存储完全初始化的单例Bean;二级缓存(earlySingletonObjects)存储提前暴露但未完全初始化的Bean;三级缓存(singletonFactories)存储Bean工厂对象。当发生循环依赖时,Spring通过三级缓存提前暴露未完全初始化的Bean实例,使相互依赖的Bean能够完成初始化。此机制仅适用于单例Bean的setter注入方式,无法解决构造器注入和原型/多例Bean的循环依赖问题。

Spring框架中的三级缓存机制及其在解决循环依赖问题中的作用

三级缓存的定义与组成

Spring框架中的三级缓存是指Spring IoC容器在创建和管理Bean实例时使用的三个Map缓存结构,它们分别是:

一级缓存(singletonObjects)

  • 存储完全初始化好的单例Bean
  • Key为Bean的名称,Value为Bean的实例
  • 也称为"单例池"
  • 当我们通过getBean()方法获取Bean时,首先会在这个缓存中查找

二级缓存(earlySingletonObjects)

  • 存储提前暴露的未完全初始化的Bean
  • Key为Bean的名称,Value为提前暴露的Bean实例
  • 主要用于解决循环依赖问题
  • 当一个Bean被提前暴露后,会先放在这个缓存中

三级缓存(singletonFactories)

  • 存储Bean工厂对象(ObjectFactory)
  • Key为Bean的名称,Value为对应的ObjectFactory
  • 当创建Bean实例时,会先将ObjectFactory放入这个缓存
  • 如果发生循环依赖,可以通过这个工厂获取提前暴露的Bean

循环依赖问题

循环依赖是指两个或多个Bean相互依赖,例如:

  • Bean A依赖于Bean B
  • Bean B又依赖于Bean A

如果没有特殊处理,这种循环依赖会导致无限递归,最终引发栈溢出异常。

三级缓存如何解决循环依赖

Spring通过三级缓存机制来解决循环依赖问题,其工作流程如下:

创建Bean A

  1. 实例化Bean A(但未进行属性填充和初始化)
  2. 将Bean A的工厂对象放入三级缓存(singletonFactories)
  3. 将Bean A标记为"正在创建"

填充Bean A的属性

  1. 发现Bean A依赖于Bean B
  2. 尝试获取Bean B

创建Bean B

  1. 实例化Bean B(但未进行属性填充和初始化)
  2. 将Bean B的工厂对象放入三级缓存(singletonFactories)
  3. 将Bean B标记为"正在创建"

填充Bean B的属性

  1. 发现Bean B依赖于Bean A
  2. 尝试获取Bean A
  3. 发现Bean A正在创建中(在三级缓存中有对应的工厂)
  4. 从三级缓存中获取Bean A的工厂,通过工厂创建提前暴露的Bean A
  5. 将提前暴露的Bean A放入二级缓存(earlySingletonObjects)
  6. 从三级缓存中移除Bean A的工厂
  7. 将提前暴露的Bean A注入到Bean B中

完成Bean B的初始化

  1. 完成Bean B的属性填充和初始化
  2. 将完全初始化的Bean B放入一级缓存(singletonObjects)
  3. 从二级缓存中移除Bean B(如果存在)

完成Bean A的初始化

  1. 将完全初始化的Bean B注入到Bean A中
  2. 完成Bean A的属性填充和初始化
  3. 将完全初始化的Bean A放入一级缓存(singletonObjects)
  4. 从二级缓存中移除Bean A

通过这种方式,Spring成功解决了循环依赖问题。关键在于提前暴露未完全初始化的Bean实例,使得其他依赖它的Bean可以先获取到它的引用,完成自己的初始化。

--- title: Spring三级缓存解决循环依赖流程 --- graph TD A["开始创建Bean A"] --> B["实例化Bean A"] B --> C["将Bean A的工厂放入三级缓存"] C --> D["标记Bean A为'正在创建'"] D --> E["填充Bean A的属性"] E --> F["发现Bean A依赖Bean B"] F --> G["尝试获取Bean B"] G --> H["Bean B不存在,开始创建Bean B"] H --> I["实例化Bean B"] I --> J["将Bean B的工厂放入三级缓存"] J --> K["标记Bean B为'正在创建'"] K --> L["填充Bean B的属性"] L --> M["发现Bean B依赖Bean A"] M --> N["尝试获取Bean A"] N --> O["发现Bean A正在创建中"] O --> P["从三级缓存获取Bean A的工厂"] P --> Q["通过工厂创建提前暴露的Bean A"] Q --> R["将提前暴露的Bean A放入二级缓存"] R --> S["从三级缓存中移除Bean A的工厂"] S --> T["将提前暴露的Bean A注入到Bean B"] T --> U["完成Bean B的初始化"] U --> V["将完全初始化的Bean B放入一级缓存"] V --> W["将Bean B注入到Bean A"] W --> X["完成Bean A的初始化"] X --> Y["将完全初始化的Bean A放入一级缓存"] Y --> Z["从二级缓存中移除Bean A"] Z --> END["完成"]
--- title: Spring三级缓存解决循环依赖时序图 --- sequenceDiagram participant Client participant Container participant Cache1 as 一级缓存<br>(singletonObjects) participant Cache2 as 二级缓存<br>(earlySingletonObjects) participant Cache3 as 三级缓存<br>(singletonFactories) participant BeanA participant BeanB Client->>Container: getBean("A") Container->>Cache1: 查找Bean A Cache1-->>Container: 未找到 Container->>BeanA: 实例化 Container->>Cache3: 存入Bean A的工厂 Container->>BeanA: 填充属性 BeanA-->>Container: 需要Bean B Container->>Cache1: 查找Bean B Cache1-->>Container: 未找到 Container->>BeanB: 实例化 Container->>Cache3: 存入Bean B的工厂 Container->>BeanB: 填充属性 BeanB-->>Container: 需要Bean A Container->>Cache1: 查找Bean A Cache1-->>Container: 未找到 Container->>Cache2: 查找Bean A Cache2-->>Container: 未找到 Container->>Cache3: 获取Bean A的工厂 Cache3-->>Container: 返回Bean A的工厂 Container->>Container: 通过工厂创建提前暴露的Bean A Container->>Cache2: 存入提前暴露的Bean A Container->>Cache3: 移除Bean A的工厂 Container->>BeanB: 注入提前暴露的Bean A Container->>BeanB: 完成初始化 Container->>Cache1: 存入完全初始化的Bean B Container->>BeanA: 注入完全初始化的Bean B Container->>BeanA: 完成初始化 Container->>Cache1: 存入完全初始化的Bean A Container->>Cache2: 移除提前暴露的Bean A Container-->>Client: 返回Bean A

三级缓存的代码实现

/** 一级缓存:存储完全初始化好的单例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集合 */
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 从一级缓存中获取完全初始化的Bean
    Object singletonObject = this.singletonObjects.get(beanName);
    
    // 2. 如果一级缓存中没有,且Bean正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 3. 从二级缓存中获取提前暴露的Bean
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 4. 从三级缓存中获取Bean工厂
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 5. 通过工厂创建提前暴露的Bean
                    singletonObject = singletonFactory.getObject();
                    // 6. 将提前暴露的Bean放入二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 7. 从三级缓存中移除Bean工厂
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 将Bean工厂放入三级缓存
            this.singletonFactories.put(beanName, singletonFactory);
            // 从二级缓存中移除(如果存在)
            this.earlySingletonObjects.remove(beanName);
        }
    }
}

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        // 将完全初始化的Bean放入一级缓存
        this.singletonObjects.put(beanName, singletonObject);
        // 从三级缓存中移除
        this.singletonFactories.remove(beanName);
        // 从二级缓存中移除
        this.earlySingletonObjects.remove(beanName);
        // 从正在创建集合中移除
        this.singletonsCurrentlyInCreation.remove(beanName);
    }
}

三级缓存机制的限制

需要注意的是,Spring的三级缓存机制只能解决单例Beansetter注入方式的循环依赖问题,对于以下情况无法解决:

构造器注入的循环依赖

  • 因为构造器注入需要在实例化时就完成依赖注入
  • 此时Bean还未创建完成,无法提前暴露

原型Bean的循环依赖

  • 原型Bean每次请求都会创建一个新的实例
  • Spring不会缓存原型Bean,因此无法解决循环依赖

多例Bean的循环依赖

  • 与原型Bean类似,多例Bean也不会被缓存
  • 因此无法解决循环依赖

总结

Spring框架中的三级缓存机制是解决循环依赖问题的关键,它通过提前暴露未完全初始化的Bean实例,使得相互依赖的Bean能够完成初始化。三级缓存分别是:

  1. 一级缓存(singletonObjects):存储完全初始化好的单例Bean
  2. 二级缓存(earlySingletonObjects):存储提前暴露的未完全初始化的Bean
  3. 三级缓存(singletonFactories):存储Bean工厂对象,用于生成提前暴露的Bean

通过这种机制,Spring能够有效地解决单例Bean的setter注入方式的循环依赖问题,提高了框架的灵活性和可用性。

参考资料

  1. Spring Framework Documentation: The IoC Container
  2. Spring源码分析:循环依赖的解决
  3. Spring Framework Reference Documentation: Circular Dependencies
  4. Spring源码解析 - 循环依赖
account_tree

思维导图

Interview AiBox logo

Interview AiBox — 面试搭档

不只是准备,更是实时陪练

Interview AiBox 在面试过程中提供实时屏幕提示、AI 模拟面试和智能复盘,让你每一次回答都更有信心。

AI 助读

一键发送到常用 AI

Spring框架中的三级缓存机制是解决循环依赖问题的核心设计。它包括三个Map缓存:一级缓存(singletonObjects)存储完全初始化的单例Bean;二级缓存(earlySingletonObjects)存储提前暴露但未完全初始化的Bean;三级缓存(singletonFactories)存储Bean工厂对象。当发生循环依赖时,Spring通过三级缓存提前暴露未完全初始化的Bean实例,使相互依赖的Bean能够完成初始化。此机制仅适用于单例Bean的setter注入方式,无法解决构造器注入和原型/多例Bean的循环依赖问题。

智能总结

深度解读

考点定位

思路启发

auto_awesome

相关题目

请介绍Java中常见的垃圾回收算法及其原理。

Java垃圾回收是JVM自动管理内存的核心机制,主要算法包括标记-清除、标记-复制、标记-整理和分代收集算法。标记-清除算法简单但会产生内存碎片;标记-复制算法高效但内存利用率低;标记-整理算法解决碎片问题但移动对象耗时;分代收集算法根据对象生命周期将内存划分为新生代和老年代,采用不同回收策略,是目前主流方案。Java提供了多种垃圾收集器如Serial、ParNew、Parallel Scavenge、CMS、G1等,选择时需考虑应用特点、内存大小和CPU资源等因素。

arrow_forward

请解释AQS(AbstractQueuedSynchronizer)是什么,它的核心原理和应用场景是什么?

AQS(AbstractQueuedSynchronizer)是Java并发包中的核心抽象类,用于构建锁和同步器。其核心原理包括:1)使用volatile int state管理同步状态;2)使用CLH队列变种管理等待线程;3)支持独占和共享两种模式;4)大量使用CAS操作保证原子性。AQS的应用场景广泛,包括构建ReentrantLock等锁、Semaphore等同步器,以及ThreadPoolExecutor等并发工具。理解AQS对掌握Java并发编程至关重要。

arrow_forward

什么是多态?

多态是面向对象编程的核心特性之一,指"同一接口,多种实现",允许不同对象对同一消息做出不同响应。多态分为编译时多态(方法重载)和运行时多态(方法重写),以及重载、参数、子类型和强制四种类型。多态提高了代码复用性、灵活性和可扩展性,降低了类之间的耦合度。实现多态通常需要继承、封装和抽象等OOP特性的支持。

arrow_forward

请解释HashMap和Hashtable的区别与联系

HashMap和Hashtable都是Java中实现Map接口的类,用于存储键值对映射。主要区别在于:HashMap是非线程安全的,允许null键和值,性能较高;Hashtable是线程安全的,不允许null键和值,性能较低。HashMap继承自AbstractMap,使用fail-fast迭代器,默认初始容量为16,扩容为2倍;Hashtable继承自Dictionary(已过时),使用非fail-fast的Enumerator,默认初始容量为11,扩容为2倍+1。两者都基于哈希表实现,提供快速查找。在现代Java开发中,HashMap通常是首选,除非有特殊线程安全需求,此时ConcurrentHashMap通常是比Hashtable更好的选择。

arrow_forward

请详细解释抽象类和接口的区别,以及它们在Java中的适用场景。

抽象类和接口是Java中实现抽象的两种机制。抽象类使用abstract关键字定义,可包含抽象方法和具体方法,支持单继承,适合代码复用和维护状态;接口使用interface关键字定义,Java 8后可包含默认方法,支持多实现,适合定义契约和解耦。抽象类表示"is-a"关系,接口表示"can-do"关系。选择时应考虑是否需要代码复用、状态维护、多重继承或契约定义等因素。

arrow_forward

阅读状态

阅读时长

8 分钟

阅读进度

5%

章节:19 · 已读:0

当前章节: 三级缓存的定义与组成

最近更新:2025-09-03

本页目录

Interview AiBox logo

Interview AiBox

AI 面试实时助手

面试中屏幕实时显示参考回答,帮你打磨表达。

免费下载download

分享题目

复制链接,或一键分享到常用平台

外部分享