Spring解决循环依赖实际就是用了个递归

本文用简要代码事例解释了Spring如何能解决依赖循环,并不讨论实际源码涉及的诸多因数,比如为什么有3个集合。

重点:

1、用3个集合来解决循环依赖,需要清楚3个集合分别是存什么

2 、开始条件 :使用 Supplier 来延迟 Bean 的创建,确保只有在需要时才创建 Bean。看下

代码中private Map<String, Supplier<?>> singletonFactories = new HashMap<>();

3 、**查找顺序:**①先从singletonObjects 找,②找不到再从earlySingletonObjects找,③再找不到从singletonFactories创建并放到earlySingletonObjects中。假设这个方法叫getBean。

4 、结束条件:从singletonFactories创建的时候遇到依赖会调用getBean获取 ,这相当于递归过程 。当遇到依赖循环的时候,递归会在调用步骤②的时候结束。相当于结束条件。

模拟代码:

java 复制代码
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

class A {
    private B b;

    public void setB(B b) {
        this.b = b;
    }

    @Override
    public String toString() {
        return "A{" +
                "b=" + (b != null ? "B" : "null") +
                '}';
    }
}

class B {
    private A a;

    public void setA(A a) {
        this.a = a;
    }

    @Override
    public String toString() {
        return "B{" +
                "a=" + (a != null ? "A" : "null") +
                '}';
    }
}

public class CircularDependencyDemo {

    private Map<String, Object> singletonObjects = new HashMap<>();
    private Map<String, Object> earlySingletonObjects = new HashMap<>();
    private Map<String, Supplier<?>> singletonFactories = new HashMap<>();

    public static void main(String[] args) {
        CircularDependencyDemo demo = new CircularDependencyDemo();
        demo.resolveCircularDependency();
    }

    public void resolveCircularDependency() {
        //创建 A 的工厂并放入 singletonFactories
        singletonFactories.put("a", () -> {
            A a = new A();
            earlySingletonObjects.put("a", a);
            //初始化 A 时发现需要 B
            a.setB(getBean("b"));
            return a;
        });

        //创建 B 的工厂并放入 singletonFactories
        singletonFactories.put("b", () -> {
            B b = new B();
            earlySingletonObjects.put("b", b);
            //初始化 B 时发现需要 A
            b.setA(getBean("a"));
            return b;
        });

        //初始化 A 和 B
        A a = getBean("a");
        B b = getBean("b");

        //完成初始化后将 A 和 B 移动到 singletonObjects
        singletonObjects.put("a", a);
        singletonObjects.put("b", b);

        System.out.println(singletonObjects.get("a"));
        System.out.println(singletonObjects.get("b"));
    }

    //查找顺序
    private <T> T getBean(String beanName) {
        if (singletonObjects.containsKey(beanName)) {
            return (T) singletonObjects.get(beanName);
        }

        if (earlySingletonObjects.containsKey(beanName)) {
            return (T) earlySingletonObjects.get(beanName);
        }

        if (singletonFactories.containsKey(beanName)) {
            Supplier<?> factory = singletonFactories.remove(beanName);
            T object = (T) factory.get();
            //这里隐藏着递归过程
            earlySingletonObjects.put(beanName, object);
            return object;
        }

        throw new RuntimeException("Bean not found: " + beanName);
    }
}

上面是基本思路,实际源码涉及过程要复杂很多:

实际递归调用并非简单的直接调getSingletion,而是...->doCreateBean->getSingletion

doCreateBean做了以下几件事情

  1. 实例化 Bean(通过构造函数或工厂方法)
  2. 提前暴露 ObjectFactory(用于解决循环依赖)
  3. 填充属性(依赖注入)
  4. 初始化(调用 init-method、Aware 接口、BeanPostProcessor 等)
  5. 注册为完整单例

AbstractAutowireCapableBeanFactory .doCreateBean

java 复制代码
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) {
    //1.实例化 Bean(调用构造函数)
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    final Object bean = instanceWrapper.getWrappedInstance();

    // ... 省略部分代码 ...

    //2.【关键】判断是否需要提前暴露 ObjectFactory(用于循环依赖)
    boolean earlySingletonExposure = (mbd.isSingleton() 
            && this.allowCircularReferences 
            && isSingletonCurrentlyInCreation(beanName));

    if (earlySingletonExposure) {
        //3.注册一个 ObjectFactory 到 singletonFactories
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    //4.填充属性(populateBean)→ 这里会触发依赖的 Bean 创建(如 A 需要 B)
    populateBean(beanName, mbd, instanceWrapper);

    //5.初始化(initializeBean)
    Object exposedObject = initializeBean(beanName, bean, mbd);

    //6.如果提前暴露了,现在要把完整的 Bean 放入 singletonObjects
    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            // 处理代理替换等逻辑...
        }
    }

    return exposedObject;
}

填充属性间接调用了doCreateBean

java 复制代码
AbstractAutowireCapableBeanFactory.populateBean()
  └─> AbstractAutowireCapableBeanFactory.applyPropertyValues()
        └─> BeanDefinitionValueResolver.resolveValueIfNecessary()
              └─> BeanDefinitionValueResolver.resolveReference()
                    └─> AbstractBeanFactory.resolveEmbeddedValue() // (非关键)
                    └─> **AbstractBeanFactory.getBean(String beanName)**
                          └─> AbstractBeanFactory.doGetBean()
                                └─> **createBean()**
                                      └─> **doCreateBean()**  ← 最终入口
相关推荐
小码哥_常2 小时前
一套自己部署的 IM,客户端是同一份 Flutter
前端·后端
学长毕设5 小时前
基于SpringBoot的实验室资产管理系统
java·spring boot·后端
zzu123zsw6 小时前
从臃肿的模板调用到声明式契约:深入 UniHttp 动态代理与多渠道生命周期扩展
java·spring boot·microsoft·unihttp
源码集结号7 小时前
云诊所门诊系统源码,基于SpringBoot+Vue.js架构,支持多租户设计与Docker容器化部署
java·spring boot·源码·医院信息系统·门诊系统·诊所系统
努力的小雨7 小时前
给 AI 眼镜开发应用有多难?我从零做了一个陪练,还上架了
后端
论文复现现场7 小时前
WRF、FEA 能用 L40S 加速吗?求解器检查、断点恢复与长期运行
java·数据库·算法·wrf·有限元分析·gpu计算·断点续算
啃瓜子的松鼠7 小时前
泛微OAe9-后端二开异步调用
java
秋天的怀念_David8 小时前
第二十一夜 · 数字黑洞与西西弗斯的惩罚
java·程序人生·算法
excel8 小时前
双 Token 与前端使用 Crypto 公私钥鉴权换 Token,哪种方案更好?
前端·后端
外收内放8 小时前
Python基础语法练习题(73)
java·前端·python