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

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

一、什么是循环依赖?

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

代码示例:

java 复制代码
@Component
class A {
    // A依赖B
    @Resource
    private B b;
}

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

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

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

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

三级缓存的定义

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

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

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

java 复制代码
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创建流程可参考下图:

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

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

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

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

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

总结

  1. Spring通过三级缓存机制解决单例Bean的循环依赖问题,核心是提前暴露半成品Bean打破依赖闭环;
  2. 三级缓存各司其职:一级缓存存成品、二级缓存存半成品、三级缓存存工厂(支持AOP延迟代理);
  3. 解决循环依赖的核心流程是:实例化Bean → 放入三级缓存 → 填充属性触发循环 → 从缓存获取半成品 → 完成初始化放入一级缓存。
相关推荐
phltxy1 天前
MCP 从协议到 Spring AI 实战
人工智能·spring·oracle
Volunteer Technology1 天前
SpringSecurity请求流转的本质
java·spring
云烟成雨TD1 天前
Spring AI 1.x 系列【42】MCP 服务端 Spring Boot 启动器
java·人工智能·spring
云烟成雨TD1 天前
Spring AI 1.x 系列【38】模型上下文协议(MCP)
java·人工智能·spring
Alson_Code1 天前
Spring AI-1.1.0
java·人工智能·后端·spring·ai编程
小小放舟、1 天前
@JsonCreator 注解详解——从枚举反序列化说起
spring boot·spring·spring cloud·java-ee·maven·intellij-idea·状态模式
摇滚侠1 天前
Spring 零基础入门到进阶 入门 06-10
java·spring·intellij-idea
總鑽風1 天前
Spring AI实战:快速集成阿里通义千问
java·后端·spring·ai编程
云烟成雨TD1 天前
Spring AI 1.x 系列【43】基于标准输入输出 (STDIO) 与服务端推送事件 (SSE) 的 MCP 服务端
java·人工智能·spring
砍材农夫1 天前
物联网实战:Spring Boot + Netty 搭建 MQTT平台 | 多协议适配与模块化设计
java·spring boot·后端·物联网·spring