图解Spring解决循环依赖

图解Spring解决循环依赖
最新回答
和快乐签约

2023-01-19 07:20:34

Spring通过三级缓存机制解决单例作用域下的循环依赖问题,其核心是利用缓存提前暴露未完全初始化的Bean实例。以下是详细解析:

一、循环依赖的场景
  1. 互相引用Bean A依赖Bean B,同时Bean B依赖Bean A:

  2. 自引用Bean A的属性依赖自身:

  3. 不支持的场景

    原型作用域(Prototype):每次创建新实例,无法缓存,直接抛出BeanCurrentlyInCreationException。

    构造器注入:Spring官方明确不支持,需改用属性注入。

二、三级缓存机制

Spring通过DefaultSingletonBeanRegistry类中的三个Map实现循环依赖的解决:

  1. 一级缓存:singletonObjects

    存放完全初始化的单例Bean,即最终可用的Bean。

  2. 二级缓存:earlySingletonObjects

    存放提前暴露的未完全初始化Bean(仅填充了属性,未执行初始化逻辑如init-method、Aware回调等)。

  3. 三级缓存:singletonFactories

    存放Bean工厂对象(ObjectFactory),用于生成未完全初始化的Bean实例(通过getObject()方法)。

三、解决流程(以Bean A依赖Bean B为例)
  1. 初始化Bean A

    创建Bean A的原始对象(调用构造器),将其包装为ObjectFactory并存入三级缓存。

    开始填充属性,发现依赖Bean B。

  2. 初始化Bean B

    创建Bean B的原始对象,存入三级缓存。

    填充Bean B的属性时,发现依赖Bean A。

  3. 从缓存获取Bean A

    检查一级缓存:未找到(Bean A未初始化完成)。

    检查二级缓存:未找到。

    检查三级缓存:找到Bean A的ObjectFactory,调用getObject()获取未完全初始化的Bean A实例,并将其移入二级缓存(同时从三级缓存移除)。

  4. 完成Bean B初始化

    将Bean B存入一级缓存,并注入到Bean A的属性中。

  5. 完成Bean A初始化

    执行Bean A的剩余初始化逻辑(如init-method),完成后存入一级缓存。

流程图示

四、关键点
  1. 三级缓存的作用

    三级缓存(singletonFactories):解决AOP代理对象的循环依赖。若Bean需要代理,ObjectFactory会返回代理对象而非原始对象。

    二级缓存(earlySingletonObjects):避免重复创建代理对象,提升性能。

  2. 为什么原型作用域不支持循环依赖

    原型Bean每次创建新实例,无法缓存中间状态,会导致无限递归创建。

  3. 构造器注入的局限性

    构造器注入时,Bean尚未实例化完成,无法提前暴露实例,因此无法支持循环依赖。

五、循环依赖的本质

循环依赖的本质是对象图构建时的顺序问题,需通过中间状态缓存解决。类似算法题中的Two Sum问题

  • Two Sum:用HashMap缓存已遍历的数字,快速查找目标值。
  • 循环依赖:用三级缓存提前暴露未完成的Bean实例,打破递归阻塞。

示例代码(简化版):

Map<String, Object> cacheMap = new HashMap<>();public Object getBean(Class<?> beanClass) { String beanName = beanClass.getSimpleName().toLowerCase(); if (cacheMap.containsKey(beanName)) { return cacheMap.get(beanName); } // 1. 创建原始对象 Object instance = beanClass.getDeclaredConstructor().newInstance(); cacheMap.put(beanName, instance); // 存入一级缓存(模拟三级缓存的提前暴露) // 2. 填充属性 for (Field field : beanClass.getDeclaredFields()) { field.setAccessible(true); Class<?> fieldClass = field.getType(); String fieldBeanName = fieldClass.getSimpleName().toLowerCase(); field.set(instance, cacheMap.containsKey(fieldBeanName) ? cacheMap.get(fieldBeanName) : getBean(fieldClass)); } return instance;}六、总结
  • Spring的解决方案:通过三级缓存提前暴露未完全初始化的Bean实例,支持单例作用域下的属性注入循环依赖。
  • 设计思想:空间换时间,用缓存打破递归阻塞。
  • 扩展思考:若需支持构造器注入的循环依赖,需引入更复杂的依赖关系分析(如改用Setter注入或重构代码结构)。