详细解析Spring如何解决循环依赖问题

简介: 详细解析 Spring 循环依赖原理,从三级缓存、核心源码到完整流程一步步讲解,带你彻底搞懂 Spring 如何解决 Bean 循环依赖及 AOP 代理场景下的实现细节。

在日常的Spring开发中,循环依赖是一个高频出现的问题,也是面试中的核心考点。本文将从概念定义、问题表现、核心原理到源码层面,全方位解析Spring是如何通过三级缓存机制优雅地解决单例Bean的循环依赖问题。

一、什么是循环依赖?

循环依赖,指的是两个或多个Bean之间互相持有对方的引用,形成闭环依赖关系。最典型的场景是Bean A依赖Bean B,同时Bean B又依赖Bean A。

代码示例:

@Component
class A {
   
    // A依赖B
    @Resource
    private B b;
}

@Component
class B {
   
    // B依赖A,形成循环
    @Resource
    private A a;
}

在默认情况下,如果Spring不做特殊处理,项目启动时会抛出BeanCurrentlyInCreationException异常,提示存在循环依赖无法解决:

循环依赖.png

二、Spring解决循环依赖的核心:三级缓存

为了解决单例Bean的循环依赖问题,Spring设计了三级缓存机制,通过提前暴露半成品Bean的方式打破依赖闭环。

三级缓存的定义

缓存级别 缓存名称 作用
一级缓存 singletonObjects 存放完全初始化完成的单例Bean(成品对象),供业务直接使用
二级缓存 earlySingletonObjects 存放提前暴露的半成品Bean(已实例化但未完成属性填充和初始化)
三级缓存 singletonFactories 存放ObjectFactory(对象工厂),这是一个函数式接口,仅在调用getObject()时才会创建Bean实例

三、核心源码解析(基于 Spring 5.3.x)

Spring处理Bean创建和循环依赖的核心逻辑集中在DefaultSingletonBeanRegistry类中,以下是关键源码及解析:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
   

    // 一级缓存:存放完全初始化好的单例Bean (成品)
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    // 三级缓存:存放Bean的工厂对象,用于创建提前暴露的Bean (半成品工厂)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    // 二级缓存:存放提前暴露的Bean实例 (半成品,未完成属性填充和初始化)
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    // 记录当前正在创建的Bean名称,解决循环依赖的关键判断
    private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

    /**
     * 核心方法:获取单例Bean(解决循环依赖的入口)
     * @param beanName Bean名称
     * @param allowEarlyReference 是否允许提前引用半成品Bean
     * @return 单例Bean实例
     */
    @Nullable
    public Object getSingleton(String beanName, boolean allowEarlyReference) {
   
        // 第一步:优先从一级缓存获取成品Bean
        Object singletonObject = this.singletonObjects.get(beanName);

        // 如果一级缓存没有,且当前Bean正在创建中(循环依赖的核心判断条件)
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
   
            // 第二步:从二级缓存获取提前暴露的半成品Bean
            singletonObject = this.earlySingletonObjects.get(beanName);

            // 如果二级缓存也没有,且允许提前引用
            if (singletonObject == null && allowEarlyReference) {
   
                // 加锁保证并发安全
                synchronized (this.singletonObjects) {
   
                    // 双重检查(防止多线程下重复创建)
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
   
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
   
                            // 第三步:从三级缓存获取ObjectFactory
                            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
   
                                // 通过工厂创建半成品Bean(提前暴露的核心操作)
                                singletonObject = singletonFactory.getObject();
                                // 将半成品Bean放入二级缓存,同时移除三级缓存(避免重复创建)
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }
        return singletonObject;
    }

    /**
     * 将Bean工厂放入三级缓存(提前暴露Bean的关键步骤)
     * 在Bean实例化后、属性填充前调用
     */
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
   
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
   
            if (!this.singletonObjects.containsKey(beanName)) {
   
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

    /**
     * 将完全初始化的Bean放入一级缓存,清理二、三级缓存
     * 在Bean初始化完成后调用
     */
    protected void addSingleton(String beanName, Object singletonObject) {
   
        synchronized (this.singletonObjects) {
   
            this.singletonObjects.put(beanName, singletonObject);
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
            this.singletonsCurrentlyInCreation.remove(beanName);
        }
    }
}

调试关键方法(建议收藏)

在实际调试Spring源码时,建议重点关注以下核心方法的调用链路:

  1. getBean() - Bean获取的入口方法
  2. doGetBean() - 获取Bean的核心实现
  3. createBean() - 创建Bean的顶层方法
  4. doCreateBean() - 创建Bean的核心逻辑
  5. createBeanInstance() - Bean实例化(创建空对象)
  6. populateBean() - Bean属性填充(依赖注入的核心)

四、循环依赖解决完整流程(以A和B为例)

结合上文A依赖B、B依赖A的场景,我们拆解Spring解决循环依赖的完整执行流程(基于单例Bean + 字段注入):

步骤 1:Spring启动,开始创建Bean A

  1. 调用getBean(A),首先将A标记为「正在创建中」(singletonsCurrentlyInCreation.add("A"));
  2. 通过反射创建A的空实例(ctro.newInstance()),此时A的属性b为null(实例化阶段);
  3. 关键操作:调用addSingletonFactory()将A的ObjectFactory放入三级缓存
  4. 开始为A填充属性,发现依赖B,触发getBean(B)

步骤 2:创建Bean B(触发循环依赖)

  1. 调用getBean(B),将B标记为「正在创建中」;
  2. 通过反射创建B的空实例(ctro.newInstance()),此时B的属性a为null;
  3. 调用addSingletonFactory()将B的ObjectFactory放入三级缓存;
  4. 开始为B填充属性,发现依赖A,再次触发getBean(A)

步骤 3:解决循环依赖(从缓存获取A)

  1. 执行getBean(A),检查一级缓存:A未完成初始化,无成品;
  2. 检查标记:A处于「正在创建中」,符合循环依赖条件;
  3. 检查二级缓存:无A的半成品实例;
  4. 检查三级缓存:存在A的ObjectFactory,调用getObject()创建A的半成品实例;
  5. 将A的半成品实例从三级缓存移至二级缓存
  6. 将半成品A返回给B,完成B的属性a填充。

步骤 4:B完成初始化,反馈给A

  1. B完成属性填充,执行初始化方法(init-method/@PostConstruct);
  2. 调用addSingleton(B),将B放入一级缓存,并清理其二、三级缓存;
  3. 将成品B返回给A,完成A的属性b填充。

步骤 5:A完成初始化,最终入池

  1. A完成属性填充,执行初始化方法;
  2. 调用addSingleton(A),将A放入一级缓存,清理其二、三级缓存;
  3. 移除A的「正在创建中」标记,循环依赖问题解决。

补充说明:
加入三级缓存后的Bean创建流程可参考下图:
三级缓存.png

五、关键细节:为什么需要三级缓存?

核心原因是为了支持AOP动态代理

  1. 延迟创建代理对象ObjectFactorygetObject()方法中会调用getEarlyBeanReference(),该方法会判断当前Bean是否需要生成AOP代理。只有发生循环依赖时,才会提前创建代理对象;
  2. 保证代理对象的唯一性:如果没有三级缓存,所有Bean都需要提前创建代理,破坏了Spring「初始化完成后再创建代理」的设计原则;
  3. 避免重复代理:三级缓存的工厂模式确保代理对象只会被创建一次,放入二级缓存后就移除三级缓存,避免重复生成。

如果仅使用二级缓存,所有Bean都必须在实例化阶段就创建代理,这会导致:

  • 代理对象创建时机提前,不符合Spring的初始化生命周期
  • 无循环依赖的Bean也会被提前代理,增加不必要的性能开销

总结

  1. Spring通过三级缓存机制解决单例Bean的循环依赖问题,核心是提前暴露半成品Bean打破依赖闭环;
  2. 三级缓存各司其职:一级缓存存成品、二级缓存存半成品、三级缓存存工厂(支持AOP延迟代理);
  3. 解决循环依赖的核心流程是:实例化Bean → 放入三级缓存 → 填充属性触发循环 → 从缓存获取半成品 → 完成初始化放入一级缓存。
目录
相关文章
|
3天前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
10508 51
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
9天前
|
人工智能 JavaScript API
解放双手!OpenClaw Agent Browser全攻略(阿里云+本地部署+免费API+网页自动化场景落地)
“让AI聊聊天、写代码不难,难的是让它自己打开网页、填表单、查数据”——2026年,无数OpenClaw用户被这个痛点困扰。参考文章直击核心:当AI只能“纸上谈兵”,无法实际操控浏览器,就永远成不了真正的“数字员工”。而Agent Browser技能的出现,彻底打破了这一壁垒——它给OpenClaw装上“上网的手和眼睛”,让AI能像真人一样打开网页、点击按钮、填写表单、提取数据,24小时不间断完成网页自动化任务。
2300 5
|
23天前
|
人工智能 JavaScript Ubuntu
5分钟上手龙虾AI!OpenClaw部署(阿里云+本地)+ 免费多模型配置保姆级教程(MiniMax、Claude、阿里云百炼)
OpenClaw(昵称“龙虾AI”)作为2026年热门的开源个人AI助手,由PSPDFKit创始人Peter Steinberger开发,核心优势在于“真正执行任务”——不仅能聊天互动,还能自动处理邮件、管理日程、订机票、写代码等,且所有数据本地处理,隐私完全可控。它支持接入MiniMax、Claude、GPT等多类大模型,兼容微信、Telegram、飞书等主流聊天工具,搭配100+可扩展技能,成为兼顾实用性与隐私性的AI工具首选。
23805 121
|
3天前
|
人工智能 IDE API
2026年国内 Codex 安装教程和使用教程:GPT-5.4 完整指南
Codex已进化为AI编程智能体,不仅能补全代码,更能理解项目、自动重构、执行任务。本文详解国内安装、GPT-5.4接入、cc-switch中转配置及实战开发流程,助你从零掌握“描述需求→AI实现”的新一代工程范式。(239字)
1853 126

热门文章

最新文章