当前位置: 首页 > news >正文

spring是怎么解决循环依赖的?

一句结论(面试开场可背)

Spring 对 singleton bean 支持循环依赖,靠的是三层缓存(三级缓存)+ 早期引用(Early Reference)机制,在创建 bean 的过程中提前暴露一个“可用引用”(可能是原生对象或代理)给其它正在创建的 bean 使用,从而打破环路。
构造器注入(constructor injection) 的循环依赖 无法被自动解决,除非使用 @LazyObjectFactory/Provider、或改写为 setter/字段注入。


核心概念:三层缓存(DefaultSingletonBeanRegistry)

Spring 在 DefaultSingletonBeanRegistry 中维护三种缓存(针对 singleton):

  1. singletonObjects(一级缓存) — 已完全创建并初始化好的单例实例(完整 bean)。

  2. earlySingletonObjects(二级缓存) — 早期曝光的单例实例(通常是实例化后、但还没完成依赖注入或初始化的对象),用于解决简单循环依赖。

  3. singletonFactories(三级缓存) — 存放 ObjectFactory(工厂方法),当需要早期引用时会调用这个工厂返回一个“早期引用”。这个工厂通常会返回经过 BeanPostProcessor(如 AOP 的 getEarlyBeanReference)处理后的代理对象,以支持代理情形下的循环依赖。

缓存关系(简化):

 
singletonFactories --create()--> earlySingletonObjects --after full init--> singletonObjects

创建流程要点(关键方法与时点,基于 AbstractAutowireCapableBeanFactory.doCreateBean)

下面按时间序列说明大致流程(已简化):

  1. 实例化(instantiateBean / createBeanInstance)

    • 调用构造器(或工厂方法)创建原始对象(尚未注入属性)。此时 BeanName 标记为“正在创建中”。

  2. 暴露早期对象工厂(重要)

    • 如果允许循环依赖且是 singleton,Spring 会在属性注入前把一个 singletonFactory 注册到 singletonFactories

       
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    • 也就是说,在 populateBean(属性注入)之前就把工厂放进三级缓存。这是实现早期曝光的关键时机。

  3. 依赖注入(populateBean)

    • Spring 为 bean 注入属性(autowire byName/byType、@Autowired 等)。当注入另一个还在创建的 bean 时,消费者会调用 getSingleton 获取该 bean 的引用:

      • getSingleton 先查一级缓存(已完成),没有则查二级缓存(earlySingletonObjects),没有就查三级(singletonFactories),若存在 factory 则调用 factory.getObject() 生成早期引用并放入二级缓存,之后移除 factory。

    • 因此,即便依赖的 bean 尚未完成初始化,也可以拿到早期引用(通常是实例或代理),从而断开环路。

  4. 初始化(initializeBean)

    • 进行 BeanPostProcessor 前置/后置处理、@PostConstructafterPropertiesSet、AOP 代理创建等。

    • 如果 AOP 需要生成代理,getEarlyBeanReference(由 SmartInstantiationAwareBeanPostProcessor)会在早期引用阶段返回代理对象,这样注入方拿到的就是代理,而不是裸对象。

  5. 注册为完全可用(addSingleton)

    • 初始化完成后,把最终实例放入一级缓存(singletonObjects),并清理二三级缓存中的相关项。


关键类 / 方法 / 作用(面试要点)

  • DefaultSingletonBeanRegistry:维护三缓存,并提供 addSingletonFactorygetSingletonregisterSingleton 等方法。

  • AbstractAutowireCapableBeanFactory#doCreateBean(...):bean 创建核心流程;在 populateBean 前调用 addSingletonFactory 暴露早期工厂(如果允许循环依赖)。

  • getEarlyBeanReference(...):调用 BeanPostProcessor(如 AOP 的 getEarlyBeanReference)以返回可能的代理,保证注入方拿到合适的引用。

  • SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference(...):AOP 使用此入口在早期生成代理,典型实现:AopInfrastructureBean / ProxyFactory

(面试举例写出以上类名会很加分)


为什么构造器注入不能自动解决?

  • 构造器注入需要在对象实例化(即调用构造函数)时就传入依赖对象;也就是说,两个相互通过构造器注入的 bean 都必须在对方构造期间得到对方的引用——这是无法通过“实例化后再注入早期引用”的机制解决的(因为早期引用是在实例化后、在属性注入前暴露的)。

  • 因此 Spring 不支持构造器循环依赖(会抛出 BeanCurrentlyInCreationException 或类似错误),除非采用以下变通办法:

    • 把其中一个依赖声明为 @Lazy(延迟获取),这样 Spring 在构造时不会立刻要求该依赖;

    • 或改用 ObjectFactory<T> / Provider<T>ApplicationContext.getBean() 在运行时延迟获取;

    • 或改成 setter/字段注入(让 Spring 能暴露早期引用并在后续注入)。


代理(AOP)与早期引用的复杂性

  • 如果某个 bean 会被 AOP 代理(比如有事务、@Transactional、或其他切面),注入方需要的是代理对象而非原始对象,否者代理相关的横切逻辑将失效。

  • 因此 Spring 在三级缓存中放入的 factory 并非简单返回原始 bean,而是调用 getEarlyBeanReference,该方法允许 SmartInstantiationAwareBeanPostProcessor(如 AbstractAutoProxyCreator)在早期暴露代理对象。这保证了注入方拿到的是代理(如果需要),从而保证 AOP 行为在循环依赖场景下仍然正确。

  • 注意:如果不使用 early-proxy(即 factory 仅返回裸对象),注入方可能在后期会被替换为代理(初始化后),这会造成注入方持有的不再是最终代理,进而出现 AOP 失效或不一致问题。Spring 的 early proxy 机制就是为了解决这种问题。


哪些场景 Spring 无法或不建议处理循环依赖

  1. 构造器注入循环依赖:不支持(unless @Lazy 或 Provider)。

  2. prototype 作用域(多例):Spring 默认不做循环引用处理,若发现循环会抛异常;因为 prototype 的生命周期复杂,容器不会缓存早期引用。

  3. final 字段 / 不可变对象:需要在构造时传入的依赖无法通过 setter 注入解决。

  4. 复杂 AOP 场景或自定义 BeanPostProcessor 未正确实现 getEarlyBeanReference:可能导致注入方拿到不正确的早期引用或代理不一致。


如何在工程中避免或稳妥处理循环依赖(最佳实践)

  • 优先用构造器注入来表达必需依赖(更清晰、便于测试);但若构造器注入导致循环,说明设计上耦合度过高,应重构(抽取接口/合并组件/引入中间层)。

  • 对可选或延迟的依赖使用 @LazyObjectProvider<T> / javax.inject.Provider / ObjectFactory<T>。这能延迟获取并破环循环。

     
    @Component public class A {private final B b;public A(@Lazy B b) { this.b = b; } }
  • 用 setter 注入或字段注入(注意测试与可见性),让 Spring 能早期暴露引用。

  • 重构以减少互相依赖:抽取第三个共同依赖(Service C),把部分逻辑移到 C。

  • 避免把事务边界放在循环依赖的 bean 上,或谨慎处理 AOP 与代理问题。

  • 使用单例设计与职责单一原则:高内聚、低耦合,减少循环依赖的出现概率。


面试常见问法与示例回答(摘要)

  • 问:Spring 是怎么解决循环依赖的?
    答(要点):

    1. 只对 singleton 生效(prototype 不支持)。

    2. 利用 DefaultSingletonBeanRegistry三级缓存(singletonObjects / earlySingletonObjects / singletonFactories)在属性注入前提前暴露早期引用(early reference)

    3. doCreateBean 的 populate 阶段之前用 addSingletonFactory 注册工厂,其他 bean 请求时会通过 factory 得到早期引用。

    4. 对于代理(AOP)会通过 getEarlyBeanReference 返回代理,保证注入方拿到的是代理而不是裸对象。

    5. 构造器注入循环依赖不能解决,应改为延迟注入或重构。

  • 给出细节(提到类名/方法名/三缓存)会显著加分。


补充:示例说明(简短伪代码)

假设 A 依赖 B,B 依赖 A,且都是 singleton 且用字段/setter 注入:

  1. 创建 A:实例化 A(对象 created)→ addSingletonFactory("A", () -> getEarlyBeanReference(A)) → populate A(需要 B)

  2. 在 populate A 时需要 B,触发创建 B:实例化 B → 因为 singletonFactory("A") 存在,B 注入 A 时会通过 getSingleton("A") 得到 earlyReference(factory 创建可能是 proxy 或 raw) → B 注入 A 的引用继续完成 → B 初始化完成并注册为 singletonObjects。

  3. 返回到 A 的 populate,A 注入 B(已完成),A 初始化完成后把 A 放到 singletonObjects(并清理 early caches)。流程顺利,循环被打破。

http://www.hskmm.com/?act=detail&tid=35933

相关文章:

  • 2025年精密球轴承厂家权威推荐榜:半导体设备/加工中心/机床主轴/直联主轴/电主轴/定制/国产高端/不锈钢/陶瓷/耐腐蚀/超高真空/真空泵/晶圆搬运机械手臂/进口替代/国产半导体/低温泵轴承精选
  • 【安徽财经大学主办】第七届管理科学信息化与经济创新发展国际学术会议 (MSIEID 2025)
  • 2025 盐城美术培训机构最新推荐榜单:涵盖全龄段课程 + 4A 信用单位,优质机构助你精准选课
  • ACCL和NCCL对比
  • 凌晨 2 点的朋友圈,她靠微擎实现了 “带娃赚钱两不误”
  • git pull中有 merge功能解释
  • 先收藏系列 工业相机的八问八答!
  • 2025年信息流代运营服务商权威推荐榜:专业投放策略与高转化效果深度解析,助力品牌精准营销
  • DeepSeek-MOE原理讲解
  • npm---查看镜像和更换镜像
  • 博弈树
  • 在一台机器上搭建一体化 Ceph 存储集群
  • byte,short,int,Long,char数据类型复习
  • 垃圾回收器总览
  • 软件工程第三次作业——结对项目
  • 2025年硅锰合金厂家推荐排行榜,硅锰合金颗粒,硅锰合金粉,高纯度硅锰合金材料源头厂家深度解析
  • PyCharm下载安装教程及激活步骤(附安装包)超详细保姆级教程
  • Windows下利用 Python OCR 识别电子发票(增值税专用发票)(使用 GhostScript 和 Tesseract )
  • 垃圾回收算法
  • 2025年臭氧检测仪厂家权威推荐榜:在线式/固定式/便携式/手持式/工业臭氧检测仪专业选购指南
  • 2025年拖鞋机厂家权威推荐榜:酒店拖鞋生产线,全自动拖鞋机,一次性拖鞋机,酒店一次性拖鞋机器专业选购指南
  • 生成式 AI 重构内容创作:从辅助工具到智能工厂 - 实践
  • 2025年不锈钢酸洗钝化液厂家推荐排行榜:环保型不锈钢清洗钝化液,不锈钢管酸洗钝化处理,不锈钢清洗剂专业选购指南
  • 达梦8加密函数是什么怎么调用,达梦数据库加密算法
  • 基于Windows,Docker用法
  • 厨房电子秤方案:厨房秤常规的功能有那些?
  • npx和npm exec有什么区别
  • MySQL 死锁 怎么处理?
  • MyBatis 的 @SelectProvider 是一个强大的注解,用于动态生成 SQL 语句
  • 跨境客服系统如何保障国际数据传输安全?