Interview AiBox logo

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

download免费下载
4local_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

相关题目

请做一个自我介绍

自我介绍是面试的开场环节,需简洁有力地展示个人背景、技能经验与岗位匹配度。有效结构包括:开场问候、核心经历、技能展示、成就亮点、岗位认知、职业规划、公司了解和得体收尾。针对运维岗位,应突出Linux管理、网络配置、自动化部署等技术能力,并结合具体案例和量化成果。表达要真诚自然,时间控制在2-3分钟,展现自信和对公司的了解。

arrow_forward

请详细介绍一下你参与的项目

项目经验介绍应包括项目背景、个人角色、技术栈、工作内容、挑战与解决方案、成果收获以及与岗位的关联。通过具体案例展示技术能力和问题解决能力,突出与运维岗位相关的经验和技能,如系统部署、监控、故障排查、自动化运维等。同时体现团队协作和持续学习的态度。

arrow_forward

请介绍一下你的项目经验

在面试中介绍项目经验时,应选择与运维岗位最相关的项目,按"项目背景→个人职责→技术栈→难点与解决方案→项目成果"的结构进行介绍。重点突出自己在项目中的技术贡献、解决问题的能力以及与运维岗位相关的经验。通过具体案例展示自己的技术实力、学习能力和团队协作精神,并将项目经验与应聘岗位联系起来,展示自己的匹配度和价值。

arrow_forward

请进行自我介绍并详细介绍你参与过的项目

自我介绍和项目经验是面试的重要环节。优秀的自我介绍应简洁明了地展示个人背景、专业技能和职业规划;项目经验介绍则应选择与岗位相关的项目,详细说明项目背景、个人职责、使用技术、解决方案和项目成果。回答时应突出与岗位相关的技能和经验,展现专业能力和解决问题的能力,同时保持自信和真诚的态度。

arrow_forward

请详细介绍你简历中提到的项目,包括实现细节和遇到的问题

面试中介绍项目经验时,应选择与运维岗位最相关的项目,按照"项目背景-个人职责-技术实现-遇到问题-解决方案-项目成果"的结构进行介绍。重点突出个人贡献、技术细节和解决问题的能力,用数据量化项目成果。示例包括校园服务器集群自动化运维平台和基于Kubernetes的微服务部署与运维两个项目,展示了监控模块设计、CI/CD流水线构建、故障排查等运维核心能力。

arrow_forward

阅读状态

阅读时长

8 分钟

阅读进度

5%

章节:19 · 已读:0

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

最近更新:2025-09-03

本页目录

Interview AiBox logo

Interview AiBox

AI 面试实时助手

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

免费下载download

分享题目

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

外部分享