自己实现springioc源码(2)-解决循环依赖问题
解决循环依赖问题思路是使用二级缓存创建了实例后先放入 earlySingletonObjects 中加载所有属性成为成功实例后再放入 singletonObjects 中并从 earlySingletonObjects 中删除下图展示了二级缓存解决循环依赖的完整流程flowchart TD A[线程 1 调用 getBean A] -- B{singletonObjects 中是否有 A?} B -- 否 -- C[实例化 A 半成品] C -- D[将 A 放入 earlySingletonObjects] D -- E[遍历 A 的字段发现依赖 B] E -- F[递归调用 getBean B] F -- G{singletonObjects 中是否有 B?} G -- 否 -- H[实例化 B 半成品] H -- I[将 B 放入 earlySingletonObjects] I -- J[遍历 B 的字段发现依赖 A] J -- K{singletonObjects 中是否有 A?} K -- 否 -- L{earlySingletonObjects 中是否有 A?} L -- 是 -- M[从 earlySingletonObjects 获取 A 半成品] M -- N[将 A 注入 B] N -- O[移除 B 半成品将 B 成品放入 singletonObjects] O -- P[返回 B 成品给 A] P -- Q[将 B 注入 A] Q -- R[移除 A 半成品将 A 成品放入 singletonObjects] R -- S[返回 A 成品] T[线程 2 调用 getBean A] -- U{singletonObjects 中是否有 A?} U -- 否 -- V{earlySingletonObjects 中是否有 A?} V -- 是 -- W[直接获取 A 半成品] W -- X[尝试调用 A 依赖的 B 的方法] X -- Y{此时 B 是否已注入 A?} Y -- 否 -- Z[出现空指针异常] Y -- 是 -- AA[正常使用 A] B -- 是 -- AB[直接返回 A 成品] G -- 是 -- AC[直接返回 B 成品] K -- 是 -- AD[直接返回 A 成品] U -- 是 -- AE[直接返回 A 成品] V -- 否 -- AF[等待线程 1 完成或重新创建]public static Map earlySingletonObjects new HashMap(); //单例对象就存放半成品类型 public static Map singletonObjects new HashMap(); // 存放成品类型其实使用一级缓存也可以搞定但是放到多线程的环境中可能就会出现空指针问题。线程 1 刚把半成品 a 放入到缓存中还未来得及将 b 注入进去。此时线程 2 直接在缓存中获取到了 a在尝试调用其所依赖的 b 的任何方法时就会出现空指针异常。代码如下public class Main3 { public static Map earlySingletonObjects new HashMap(); // 存放成品类型 public static Map singletonObjects new HashMap(); // 单例对象就存放半成品类型 private static Object getSingleton(String className) { Object singletonObject singletonObjects.get(className); if (singletonObject null) { singletonObject earlySingletonObjects.get(className); } return singletonObject; } private static T T getBean(ClassT clazz) throws IllegalAccessException, InstantiationException { // 优先从缓存中获取 Object object getSingleton(clazz.getName()); if (object ! null) { return (T) object; } synchronized (singletonObjects) { object singletonObjects.get(clazz.getName()); if (object ! null) { return (T) object; } // 实例化对象 T instance clazz.newInstance(); earlySingletonObjects.put(clazz.getName(), instance); // 实例化后就放入缓存 // 获取当前类中的所有字段 Field[] fields clazz.getDeclaredFields(); for (Field field : fields) { // 允许访问私有变量 field.setAccessible(true); // 判断字段是否被 Load 注解修饰 boolean isUseLoad field.isAnnotationPresent(Load.class); if (!isUseLoad) { continue; } // 获取需要被注入的字段的 class Class? fieldType field.getType(); // 递归获取字段的实例对象 Object fieldBean getBean(fieldType); // 将实例对象注入到该字段中 field.set(instance, fieldBean); } earlySingletonObjects.remove(clazz.getName()); singletonObjects.put(clazz.getName(), instance); return instance; } } public static void main(String[] args) throws InstantiationException, IllegalAccessException { /* A a getBean(A.class); System.out.println(a.getB().getClass()); */ new Thread(() - { try { A a1 getBean(A.class); System.out.println(t1.a: a1.hashCode()); System.out.println(t1.b: a1.getB().hashCode()); } catch (IllegalAccessException | InstantiationException e) { e.printStackTrace(); } }).start(); new Thread(() - { try { A a1 getBean(A.class); System.out.println(t2.a: a1.hashCode()); System.out.println(t2.b: a1.getB().hashCode()); } catch (IllegalAccessException | InstantiationException e) { e.printStackTrace(); } }).start(); new Thread(() - { try { B b getBean(B.class); System.out.println(t3.b: b.hashCode()); System.out.println(t3.a: b.getA().hashCode()); } catch (IllegalAccessException | InstantiationException e) { e.printStackTrace(); } }).start(); } }public class A { Load private B b; public B getB() { return b; } } public class B { Load private A a; public A getA() { return a; } }三级缓存与二级缓存的对比二级缓存虽然能解决普通的循环依赖问题但在涉及 AOP 代理时存在明显不足。下面通过表格对比二级缓存与三级缓存在解决循环依赖、AOP 代理和并发安全方面的差异对比维度二级缓存三级缓存解决循环依赖可以解决普通单例 Bean 的循环依赖通过 earlySingletonObjects 暴露半成品供依赖方提前获取。同样可以解决循环依赖且处理方式更灵活通过 ObjectFactory 延迟获取目标对象。AOP 代理无法解决 AOP 代理问题。半成品直接放入缓存无法在注入前生成代理对象导致代理失效或注入原始对象。可以解决 AOP 代理问题。通过三级缓存的 ObjectFactory 在需要时才创建代理确保注入的是代理对象。并发安全earlySingletonObjects 未加锁多线程并发读取半成品时可能出现空指针异常。通过 synchronized 和 ObjectFactory 的延迟创建机制在并发场景下能更好地保证线程安全。Spring 之所以需要三级缓存核心原因在于AOP 代理对象的创建时机。当 Bean 需要被代理时代理对象必须在属性注入之前生成否则注入到依赖方中的将是原始对象而非代理对象。三级缓存通过 ObjectFactory 将代理的创建延迟到真正需要注入的时刻既保证了循环依赖的解决又确保了 AOP 代理的正确性。而二级缓存由于缺少这一层延迟机制无法在注入前动态生成代理因此 Spring 必须引入三级缓存来兼顾循环依赖与 AOP 代理的双重需求。总结二级缓存解决循环依赖的核心思路可以概括为以下几点实例化后立即放入 earlySingletonObjects 暴露半成品对象创建后先以半成品形式放入缓存供依赖方提前获取引用。属性注入时从缓存获取依赖对象递归创建依赖对象时优先从缓存中查找避免重复实例化。成品完成后移入 singletonObjects所有属性注入完成后将成品放入正式缓存并移除半成品。二级缓存无法解决 AOP 代理问题涉及代理对象时需要引入三级缓存来延迟创建代理。多线程下需注意 earlySingletonObjects 的并发访问半成品缓存未加锁并发读取时可能出现空指针异常。

相关新闻

一文带你搞懂Nginx,老王出品,必属精品

一文带你搞懂Nginx,老王出品,必属精品

前言:Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务: 一、nginx 基础常用命令: 1. ./nginx -t #检查配置文件的语法的正确性,并尝试打开配置文件中所引用到的文件。 2. ./n…

2026/10/11 13:11:36 阅读更多 →
开发第一线:新Windows与开发环境:大模型LLM/量化 Win-Mac统一快捷键体验 win强制关机:shutdown /s /f /t 0

开发第一线:新Windows与开发环境:大模型LLM/量化 Win-Mac统一快捷键体验 win强制关机:shutdown /s /f /t 0

对于开发者用Windows来说,最大的建议就是不要用Windows,既然必须要用,那就来吧!!! win强制关机:shutdown /s /f /t 0 一、必装软件 1.搜狗/百度输入法,-> 双拼 2.Chrome浏览器 3.Everything (墙裂推荐!!) 4.wsl + git for windows 5.anaconda3 + vs c…

2026/10/10 8:12:42 阅读更多 →
基于PCA9422与STM32的便携设备电源管理方案设计

基于PCA9422与STM32的便携设备电源管理方案设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 8:11:42 阅读更多 →

最新新闻

Django+Vue外卖点餐系统毕设指南:从建表到联调全流程

Django+Vue外卖点餐系统毕设指南:从建表到联调全流程

简介:一套基于Python Django与Vue.js开发的外卖点餐系统毕业设计项目,采用B/S架构,适合计算机相关专业学生作为毕业设计或课程设计参考。前端覆盖首页、菜品详情、订单中心、用户中心等核心用户场景;后台提供总览、订单管理、菜品…

2026/10/11 13:14:52 阅读更多 →
如何在 Virtual Mac 上 5 分钟装好 macOS:新手保姆级快速上手教程

如何在 Virtual Mac 上 5 分钟装好 macOS:新手保姆级快速上手教程

【免费下载链接】VirtualMacOniPad People have dreamed of running macOS on iPad for more than a decade. Today, that dream comes true. With Virtual Mac, iPad finally breaks free from iPadOS, enabling pro apps like Xcode, Terminal, Final Cut Pro, Logic Pro, an…

2026/10/11 13:14:52 阅读更多 →
Kubernetes弹性伸缩实战:HPA、VPA与Cluster Autoscaler原理与配置

Kubernetes弹性伸缩实战:HPA、VPA与Cluster Autoscaler原理与配置

这两年只要跟Kubernetes沾边的团队,几乎都会聊到弹性伸缩。我这些年在一线折腾过不少集群,从最早以为加个 HPA 就万事大吉,到后来被线上抖动、扩容滞后和成本账单来回折磨,才算是把这一整套机制吃透。Kubernetes弹性伸缩实际上不是…

2026/10/11 13:14:52 阅读更多 →
Kubernetes弹性伸缩实战:HPA、VPA、CA与KEDA解析

Kubernetes弹性伸缩实战:HPA、VPA、CA与KEDA解析

做 Kubernetes 这行绕不开的话题就是弹性伸缩。很多人觉得只要部署到集群里,流量大了自然就能扩容,结果一到促销或者突发流量就被报警轰醒,才发现 Pod 数量没有动,节点也快被打满了。原因很简单,Kubernetes 的弹性伸缩…

2026/10/11 13:14:52 阅读更多 →
把CIContext存进@State?SwiftUI-Agent-Skill的“状态即缓存“高级技巧解析

把CIContext存进@State?SwiftUI-Agent-Skill的“状态即缓存“高级技巧解析

【免费下载链接】SwiftUI-Agent-Skill SwiftUI agent skill for Claude Code, Codex, and other AI tools. 项目地址: https://gitcode.com/GitHub_Trending/swi/SwiftUI-Agent-Skill 点击查看 免费下载 SwiftUI-Agent-Skill(技能名 SwiftUI Pro&#x…

2026/10/11 13:14:52 阅读更多 →
健身动作错误归因数据集:专注关节抖动、遮挡漂移与小目标定位

健身动作错误归因数据集:专注关节抖动、遮挡漂移与小目标定位

简介:本资源是面向计算机视觉开发者与运动健康AI研究者的健身动作关键点检测专用数据集,聚焦于自下而上类动作识别与姿态评估,解决健身动作自动判别、姿势纠错与虚拟教练系统构建等核心问题。数据集共1758张真实场景图像(含训练/验…

2026/10/11 13:13:51 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →