惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
爱范儿
爱范儿
H
Help Net Security
V
Visual Studio Blog
J
Java Code Geeks
Stack Overflow Blog
Stack Overflow Blog
Microsoft Security Blog
Microsoft Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
MyScale Blog
MyScale Blog
The Cloudflare Blog
Martin Fowler
Martin Fowler
D
Docker
腾讯CDC
F
Fortinet All Blogs
雷峰网
雷峰网
GbyAI
GbyAI
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Blog — PlanetScale
Blog — PlanetScale
Engineering at Meta
Engineering at Meta
博客园 - 聂微东
博客园 - 叶小钗

博客园_首页

Linux实操--组管理、权限管理和定时任务 Java + EasyExcel 实现单个接口导出多个Excel Mem0 源码解析系列(二):提示词工程的深度剖析 Openclaw TaskFlow究竟是什么?和普通Skill技能有什么区别 博文阅读密码验证 - 博客园 嘉立创开源:应该是全网MicroPython教程最多的开发板 Hermes Agent 集成实践:从协议到生产 2026年AI编程工具横评:Cursor、Codex、Claude Code、Zed、Windsurf Java程序员必看的RAG入门教程 2026 AI效率神器:Superpowers + Claude Code 保姆级教程 本地大模型部署全攻略:从 0 到 1 玩转 Ollama 【从0到1构建一个ClaudeAgent】内存管理-上下文压缩 .NET 高级开发 | 设计、实现一个事件总线框架 电子小白入门之NE555 3. WorkBuddy:隐藏玩法,一键召唤专家,让 AI 以"专家身份"给你干活 和AI一起搞事情#3:Claude Teammate 游戏开发翻车实录 【OpenClaw】通过 Nanobot 源码学习架构---(7)Memory C# .NET 周刊|2026年3月3期 我在 Debian 11 上把 K8s 单机搭起来了,过程没你想的那么顺(/opt 目录版) 深度学习进阶(七)Data-efficient Image Transformer CLI+Skill搭建浏览器AI自动化框架,告别一切重复枯燥任务 告别Token账单无底洞:OpenClaw本地部署,重塑企业数据主权的唯一解 FastAPI+Vue:文件分片上传+秒传+断点续传,这坑我帮你踩平了! SBTI 爆火后,我做了个程序员版的 CBTI。。已开源 + 附开发过程 多模态检索开始进入工程期:用 Sentence Transformers 搭建可落地的 Multimodal RAG 100多行代码实现一个最简单的Agent(用ReAct) Claude Code 通关手册(八):推荐 5 个 Hooks,代码质量提升 3 倍 老板:“有人截图了!”。安全部门:“收到,马上查暗水印!” - why技术 技术之外,皆是人间 C#/.NET/.NET Core技术前沿周刊 | 第 69 期(2026年4.01-4.12)
Spring循环依赖
风倒梧桐树 · 2026-06-16 · via 博客园_首页

Spring 循环依赖与三级缓存

一、什么是循环依赖

1.1 定义

循环依赖是指两个及两个以上的 Bean 互相持有对方引用,形成闭环依赖关系

典型场景:A 依赖 B、B 依赖 A;或多 Bean 闭环:A→B→C→A。

1.2 产生问题

Spring 单例 Bean 创建核心流程:先实例化原始对象 → 再填充属性依赖

在互相依赖场景下会形成递归死循环:

创建 A 需要先注入 B → 创建 B 需要先注入 A → 无限递归,最终抛出创建异常。

1.3 核心适配规则(高频)

Spring 三级缓存仅能解决:单例 Bean + Setter/字段注入(@Autowired)的循环依赖

无法解决、直接抛出 BeanCurrentlyInCreationException 的场景:

  • 构造方法注入(实例化阶段执行,还未创建三级缓存)

  • 多例(Prototype)Bean(不进入任何缓存)

  • 部分工厂方法、@Lookup 动态依赖场景

二、单例 Bean 完整标准生命周期(三级缓存原理基础)

Spring 单例 Bean 正常创建全流程,也是循环依赖解决的核心时序依据:

2.1 完整执行步骤

  1. 实例化 createBeanInstance:通过构造方法 new 出原始裸对象,仅分配内存,所有属性为空。

  2. 提前暴露半成品 Bean(核心):实例化完成、属性填充之前,执行 addSingletonFactory,将包装了早期代理逻辑的 ObjectFactory 放入三级缓存。

    • 存入工厂时不会主动创建代理,实现代理懒加载

    • 仅在发生循环依赖时,才会触发工厂执行代理生成

  3. 属性填充 populateBean:解析 @Autowired、Setter 方法,注入所依赖的 Bean。

  4. 初始化 initializeBean(固定执行顺序):

    1. 初始化前置后置处理器:postProcessBeforeInitialization

    2. 自定义初始化方法:@PostConstructInitializingBean#afterPropertiesSet → 自定义 init-method

    3. 初始化后置后置处理器:postProcessAfterInitialization(常规 AOP 代理默认创建入口)

  5. 存入一级缓存:将完全初始化、可对外使用的成品 Bean 放入一级缓存,同时清空二、三级缓存对应记录。

2.2 AOP 代理创建时机关键区别

  • 无循环依赖:代理在 postProcessAfterInitialization 中统一创建

  • 有循环依赖:代理提前通过三级缓存工厂创建,后置处理器不再重复生成

三、三级缓存结构、作用与互斥规则

三级缓存均位于 DefaultSingletonBeanRegistry 核心类中,三者互斥存在,同一个 BeanName 不会同时存在多级缓存中。

3.1 一级缓存:singletonObjects

  • 存储内容:完全初始化完毕的成品 Bean

  • 状态:已完成实例化、属性填充、初始化、代理创建

  • 作用:全局唯一对外提供可用的单例 Bean,用户最终获取的 Bean 均来自一级缓存

3.2 二级缓存:earlySingletonObjects

  • 存储内容:提前暴露的半成品 Bean(原始裸对象 / 提前生成的早期 AOP 代理)

  • 状态:未完成属性填充、未完成初始化

  • 作用:缓存已经通过三级工厂生成的早期对象,避免重复调用 ObjectFactory.getObject() 重复创建代理,提升性能

3.3 三级缓存:singletonFactories

  • 存储内容:Bean 的工厂对象 ObjectFactory

  • 核心逻辑:工厂持有原始裸 Bean,内部封装 getEarlyBeanReference 早期代理创建逻辑

  • 执行时机:无循环依赖时,工厂永久不执行;仅发生循环依赖,其他 Bean 需要提前获取当前 Bean 时,才调用 getObject()

  • 核心价值:实现代理懒加载,解决 AOP 场景下循环依赖产生多代理对象的问题

3.4 缓存更替规则(核心)

  1. Bean 实例化后,存入三级缓存,此时一、二级缓存无数据

  2. 触发循环依赖时,三级工厂生成对象/代理,存入二级缓存,删除三级缓存记录

  3. Bean 完全初始化完成后,存入一级缓存,删除二、三级缓存记录

3.5 循环依赖判断依据

Spring 维护集合 singletonsCurrentlyInCreation,存储正在创建中的 BeanName。

属性填充时,若发现依赖的 Bean 在该集合中,判定为循环依赖,触发三级缓存解析逻辑。

四、无 AOP 代理场景:三级缓存解决循环依赖流程

Spring三级缓存工作流程
图源:rocky-fang - Spring循环依赖文章

4.1 场景

单例 Bean A、B 互相字段/Setter 注入,无 AOP 代理。

4.2 完整执行流程

  1. 创建 A:实例化原始 A 对象,存入三级缓存工厂;属性填充阶段发现依赖 B,转而创建 B。

  2. 创建 B:实例化原始 B 对象,存入三级缓存工厂;属性填充阶段发现依赖 A。

  3. 容器检测到 A 处于创建中,从 A 的三级缓存取出 ObjectFactory,执行 getObject(),无代理逻辑,直接返回原始 A 半成品对象。

  4. 将原始 A 存入二级缓存,删除 A 的三级缓存工厂,完成 A 注入 B 的操作。

  5. B 完成全部属性填充、初始化流程,存入一级缓存,成为成品 Bean。

  6. 回到 A 的创建流程,注入完整的成品 B,A 走完初始化流程,最终存入一级缓存,循环依赖解决。

五、带 AOP 代理场景:三级缓存核心价值(重点)

当 Bean 需要 AOP 代理(事务、切面等),必须依赖三级缓存,仅靠二级缓存会产生严重问题。

5.1 核心 AOP 组件与方法

自动代理核心类:AnnotationAwareAspectJAutoProxyCreator

  • getEarlyBeanReference():仅三级缓存工厂触发

    • earlyProxyReferences 存入标记,记录当前 Bean 已提前生成早期代理

    • 调用 wrapIfNecessary() 生成代理对象并返回

  • postProcessAfterInitialization():常规代理创建入口

    • 读取 earlyProxyReferences 标记,若已提前创建代理,直接返回,避免重复生成

5.2 带 AOP 完整循环依赖流程

  1. 实例化原始 A,将封装代理逻辑的 ObjectFactory 存入三级缓存,此时不生成代理

  2. A 属性填充需要 B,启动 B 的创建流程。

  3. 实例化原始 B,存入三级缓存工厂;B 属性填充需要 A。

  4. 容器检测 A 正在创建,取出 A 的三级缓存工厂,执行 getObject(),触发 getEarlyBeanReference

  5. 打上已代理标记,通过 wrapIfNecessary 生成早期代理对象 proxyA。

  6. proxyA 存入二级缓存,删除 A 的三级缓存工厂,将 proxyA 注入 B。

  7. B 完成所有创建流程,存入一级缓存。

  8. 回到 A,注入完整成品 B,执行初始化流程。

  9. 执行后置处理器时,检测到 A 已有代理标记,不再重复创建代理。

  10. 最终将二级缓存中唯一的 proxyA 存入一级缓存,全局代理对象唯一。

5.3 为什么不能只用二级缓存?(核心原理)

假设舍弃三级缓存,实例化 Bean 后直接生成代理放入二级缓存:

  1. 手动调用 wrapIfNecessary 生成代理,绕开 getEarlyBeanReference不会添加代理标记

  2. B 注入该代理对象,完成依赖填充,B 存入一级缓存。

  3. A 继续执行初始化后置处理,检测无代理标记,再次生成一个全新的代理对象

  4. 最终结果:B 持有的代理、一级缓存中的代理是两个不同对象

5.4 多代理对象导致的线上问题

  • @Transactional 事务失效

  • 切面逻辑重复执行/不执行

  • 并发锁、本地缓存数据不一致

  • 对象引用不统一引发各种诡异 Bug

六、终极总结(背诵版)

  1. 循环依赖解决范围:仅单例 + Setter/字段注入可解,构造注入、多例 Bean 直接报错。

  2. 三级缓存分工:一级存成品、二级存早期半成品、三级存代理工厂(懒加载)。

  3. 二级缓存作用:优化性能,避免循环依赖时重复执行工厂方法创建代理。

  4. 三级缓存核心意义:延迟代理创建、打上全局标记,保证整个容器中 Bean 代理对象唯一

  5. 核心设计思想:无循环依赖则不提前创建代理,兼顾性能与循环依赖场景的兼容性。