通俗易懂的讲解Spring循环依赖(三级缓存)

📕我是廖志伟,一名Java开发工程师、《Java项目实战——深入理解大型互联网企业通用技术》(基础篇)、(进阶篇)、(架构篇)清华大学出版社签约作家、Java领域优质创作者、优快云博客专家、阿里云专家博主、51CTO专家博主、产品软文专业写手、技术文章评审老师、技术类问卷调查设计师、幕后大佬社区创始人、开源项目贡献者。

📘拥有多年一线研发和团队管理经验,研究过主流框架的底层源码(Spring、SpringBoot、SpringMVC、SpringCloud、Mybatis、Dubbo、Zookeeper),消息中间件底层架构原理(RabbitMQ、RocketMQ、Kafka)、Redis缓存、MySQL关系型数据库、 ElasticSearch全文搜索、MongoDB非关系型数据库、Apache ShardingSphere分库分表读写分离、设计模式、领域驱动DDD、Kubernetes容器编排等。不定期分享高并发、高可用、高性能、微服务、分布式、海量数据、性能调优、云原生、项目管理、产品思维、技术选型、架构设计、求职面试、副业思维、个人成长等内容。

Java程序员廖志伟

🌾阅读前,快速浏览目录和章节概览可帮助了解文章结构、内容和作者的重点。了解自己希望从中获得什么样的知识或经验是非常重要的。建议在阅读时做笔记、思考问题、自我提问,以加深理解和吸收知识。阅读结束后,反思和总结所学内容,并尝试应用到现实中,有助于深化理解和应用知识。与朋友或同事分享所读内容,讨论细节并获得反馈,也有助于加深对知识的理解和吸收。💡在这个美好的时刻,笔者不再啰嗦废话,现在毫不拖延地进入文章所要讨论的主题。接下来,我将为大家呈现正文内容。

优快云


场景设定‌

假设你开了一家玩具工厂,要组装两种玩具:

  • 变形金刚‌(需要电池)
  • 电池‌(需要装在变形金刚里)

但问题来了:

  • 组装变形金刚‌的时候,需要先把电池装进去
  • 生产电池‌的时候,又需要知道电池要装到哪个变形金刚上

这就像Spring中的两个Bean(变形金刚和电池)‌互相依赖‌,必须先有对方才能完成自己。

工厂的三级流水线(对应Spring三级缓存)‌

工厂为了解决这个问题,设立了三道流水线:

流水线作用对应Spring技术
设计部‌(三级缓存)存玩具的设计图纸(告诉工人怎么组装半成品)ObjectFactory(生成对象的工厂方法)
半成品区‌(二级缓存)存组装一半的玩具(比如没装电池的变形金刚)earlySingletonObjects(早期对象引用)
成品区‌(一级缓存)存完整的玩具(可以直接卖)singletonObjects(完整Bean)

解决问题流程‌

步骤1:开始组装变形金刚‌

工人想生产变形金刚,先设计一个「没有电池的变形金刚」图纸(实例化空对象)
把图纸存到设计部(‌三级缓存‌),此时:

三级缓存 = { "变形金刚": 生产图纸 }  

步骤2:发现需要电池‌

工人去成品区(一级缓存)找电池 → ‌无货‌
启动电池生产线,生成「没装变形金刚的电池」图纸存入设计部(三级缓存):

三级缓存 = { 
  "变形金刚": 生产图纸,  
  "电池": 生产图纸 
}  

步骤3:电池需要变形金刚(重点优化)‌

电池生产线需要知道电池装到哪个变形金刚上
按顺序检查缓存‌:
第一站‌:成品区(一级缓存) → 无变形金刚
第二站‌:半成品区(二级缓存) → 空(首次生产)
第三站‌:设计部(三级缓存) → 找到变形金刚图纸
执行图纸生成变形金刚空壳:
空壳特性‌:
▸ 已实例化但未装电池(未注入属性)
▸ ‌携带唯一序列号‌(内存地址固定,如#1234)
关键操作‌:

存入二级缓存 → { "变形金刚": 空壳#1234 }  
移除三级缓存 → { "电池": 生产图纸 }  

AOP代理预判‌:
如果变形金刚需要「隐身模式」(如事务代理):
▸ ‌直接给空壳#1234披上隐身衣‌(提前生成代理对象)
▸ 隐身衣的序列号仍是#1234(代理对象地址不变)

步骤4:完成电池生产(补充早期引用说明)‌

从二级缓存取出变形金刚空壳#1234
电池与空壳建立绑定‌:
电池通过USB接口写入型号(调用setBatteryType()方法)
此时变形金刚仍是空壳,但序列号#1234已记录在电池中‌
禁止操作‌:
▸ ❌ 尝试调用变形金刚.启动引擎()(依赖未注入的电池)
▸ ✅ 允许调用变形金刚.注册电池()(仅需内存写入)
电池完工存入一级缓存:

一级缓存 = { "电池": 成品电池 }  
二级缓存保持 = { "变形金刚": 空壳#1234 }  

步骤5:完成变形金刚生产(强调引用一致性)‌

从一级缓存获取电池,将其插入空壳#1234的能源舱(属性注入)
动态升级操作‌:
如果需添加「自动修复」功能(如日志AOP):
▸ ‌直接给#1234安装修复模块‌(后续生成代理)
▸ 序列号仍为#1234(对象地址不变)
执行最终质检(@PostConstruct初始化):
测试变形功能(方法逻辑验证)
充能武器系统(依赖注入后的操作)
缓存状态更新‌:

一级缓存 = { "电池": 成品电池, "变形金刚": 完整品#1234 }  
二级缓存 = 空  

早期引用依然有效‌:
电池内记录的#1234序列号‌指向升级后的完整品‌
无需重新插拔电池(对象引用始终一致)


缓存状态跟踪表(全局视角)‌

步骤一级缓存(成品区)二级缓存(半成品区)三级缓存(设计部)
1变形金刚图纸
2变形金刚+电池图纸
3变形金刚空壳电池图纸
4电池变形金刚空壳
5电池+变形金刚

为什么必须三级?‌

设计图纸(三级缓存)‌:

  • 如果直接生产半成品,遇到特殊需求(比如给变形金刚喷漆),后面可能改不动了
  • Spring中对应‌AOP代理‌:需要延迟生成代理对象,避免类型冲突

半成品区(二级缓存)‌:

  • 防止多个工人重复组装半成品(比如同时生产10个变形金刚,不需要10次半成品)

成品区(一级缓存)‌:

  • 保证所有人拿到的都是最终版本(避免有的工人拿到半成品,有的拿到成品)

哪些情况工厂解决不了?‌

  • 必须同时组装‌:如果变形金刚和电池‌必须同时组装‌(构造器注入),流水线无法分步操作

  • 定制化生产‌:每次生产新玩具都要从头开始(原型模式Bean)

  • 三人互锁‌:如果「变形金刚→电池→马达→变形金刚」三人循环,流水线可能卡死

一句话总结‌

Spring的三级缓存就像工厂的流水线分级,通过「先造空壳 → 暂存半成品 → 最后补全」的方式,破解了“先有鸡还是先有蛋”的死循环。

工厂通过「先造壳、再填肉」的策略破解死锁:

  • 变形金刚先造空壳‌ → 让电池能注册信息
  • 电池利用空壳完成自己‌ → 独立存为成品
  • 变形金刚用成品电池补全自己‌ → 最终覆盖空壳

整个过程依赖三级缓存的‌状态隔离‌和‌引用传递机制‌,确保两个生产线既交叉依赖又互不阻塞。

优快云

📥博主的人生感悟和目标

Java程序员廖志伟

希望各位读者大大多多支持用心写文章的博主,现在时代变了,信息爆炸,酒香也怕巷子深,博主真的需要大家的帮助才能在这片海洋中继续发光发热,所以,赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!

📙经过多年在优快云创作上千篇文章的经验积累,我已经拥有了不错的写作技巧。同时,我还与清华大学出版社签下了四本书籍的合约,并将陆续出版。这些书籍包括了基础篇进阶篇、架构篇的📌《Java项目实战—深入理解大型互联网企业通用技术》📌,以及📚《解密程序员的思维密码–沟通、演讲、思考的实践》📚。具体出版计划会根据实际情况进行调整,希望各位读者朋友能够多多支持!

🔔如果您需要转载或者搬运这篇文章的话,非常欢迎您私信我哦~

<think>好的,用户现在想了解IOC容器的原理。之前我给他们讲解了Maven的pom.xml依赖管理,现在他们可能在学习Spring框架,所以需要理解IOC的基本概念和工作机制。首先,我需要确认用户对IOC的了解程度,可能他们是刚开始接触,所以需要从基础讲起,但也要结合实际例子,避免太抽象。 用户的需求是了解IOC容器原理,所以我需要分解IOC的核心概念,比如控制反转、依赖注入,以及容器的工作流程。可能需要对比传统编程方式,让用户明白为什么需要IOC。比如,传统方式中对象自己创建依赖,导致耦合度高,而IOC通过容器管理对象的创建和依赖注入,降低耦合。 接下来,要解释IOC容器的三个基本步骤:配置元数据、实例化容器、获取Bean。这里需要结合具体的配置方式,比如XML、注解或者Java配置,可能用户之前接触过XML配置,所以用XML例子会更直观。同时,要提到Bean的生命周期,比如初始化方法和销毁方法的执行顺序。 用户可能想知道IOC容器如何解决依赖注入的问题,比如构造器注入和Setter注入的区别,以及自动装配的机制。需要举例说明,比如一个UserService依赖UserRepository,容器如何自动注入这个依赖。此外,可能需要提到循环依赖的问题,以及Spring是如何解决这个问题的,比如三级缓存机制。 还要考虑用户可能对专业术语不太熟悉,需要用通俗易懂的语言解释。例如,将IOC容器比作工厂,负责创建和管理对象,而开发者只需定义好对象之间的关系。同时,需要强调IOC的好处,比如提高可维护性、可测试性,以及降低组件之间的耦合。 可能用户在学习过程中会遇到依赖注入的问题,比如如何配置Bean,或者遇到注入失败的情况,所以需要给出一些实践建议,比如使用@Component注解,或者配置@ComponentScan来让容器扫描组件。此外,可以提到常见的注解如@Autowired和@Qualifier的作用,帮助用户更好地理解自动装配的过程。 最后,通过一个简单的代码示例,展示传统方式与IOC方式的区别,让用户直观地看到IOC如何管理对象。同时,思考题可以引导用户自己思考依赖注入的方式,巩固他们的理解。需要确保整个解释逻辑清晰,步骤分明,帮助用户逐步构建对IOC容器的理解。</think>我将通过一个完整的Spring框架实例,结合代码演示和原理图示,深入解析IOC容器的工作机制。以下内容分为核心原理、工作流程、具体实现三个层次: --- ### 一、传统编程模式 vs IOC模式 **传统模式(主动控制)**: ```java // 直接创建依赖对象 UserRepository userRepo = new MySQLUserRepository(); UserService userService = new UserService(userRepo); ``` **IOC模式(被动接收)**: ```java // 从容器获取已装配好的对象 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = context.getBean(UserService.class); ``` --- ### 二、IOC容器三大核心功能 1. **对象创建**:管理Bean的生命周期(创建、初始化、销毁) 2. **依赖注入**:自动处理对象间的依赖关系 3. **配置管理**:统一管理应用程序组件 --- ### 三、容器工作原理(四步流程) ```mermaid sequenceDiagram participant Developer participant Container participant Config participant Bean Developer->>Config: 1.定义配置元数据 Developer->>Container: 2.创建容器实例 Container->>Config: 3.读取配置信息 Container->>Bean: 4.实例化并装配对象 Container-->>Developer: 返回可用Bean ``` --- ### 四、配置元数据示例 **XML配置方式**: ```xml <!-- beans.xml --> <beans> <bean id="userRepo" class="com.example.MySQLUserRepository"/> <bean id="userService" class="com.example.UserService"> <constructor-arg ref="userRepo"/> </bean> </beans> ``` **注解配置方式**: ```java @Configuration public class AppConfig { @Bean public UserRepository userRepo() { return new MySQLUserRepository(); } @Bean public UserService userService() { return new UserService(userRepo()); } } ``` --- ### 五、依赖注入的三种方式 1. **构造器注入**(推荐方式): ```java public class UserService { private final UserRepository userRepo; public UserService(UserRepository userRepo) { this.userRepo = userRepo; } } ``` 2. **Setter方法注入**: ```java public class UserService { private UserRepository userRepo; public void setUserRepo(UserRepository userRepo) { this.userRepo = userRepo; } } ``` 3. **字段注入**(通过反射实现): ```java public class UserService { @Autowired private UserRepository userRepo; } ``` --- ### 六、容器内部工作机制(源码级解析) 1. **BeanDefinition解析**: ```java // 伪代码示例 Map<String, BeanDefinition> beanDefinitions = parseConfiguration(config); ``` 2. **依赖关系处理**: ```java for (BeanDefinition bd : beanDefinitions.values()) { resolveDependencies(bd); } ``` 3. **Bean实例化**: ```java Object beanInstance = createBeanInstance(bd.getClassName()); ``` 4. **依赖注入执行**: ```java populateBean(beanInstance, bd.getDependencies()); ``` 5. **初始化回调**: ```java if (implements InitializingBean) { beanInstance.afterPropertiesSet(); } ``` --- ### 七、高级特性解析 1. **作用域管理**: - Singleton(默认):单个容器内唯一实例 - Prototype:每次请求创建新实例 - Request/Session(Web环境) 2. **生命周期回调**: ```java public class ExampleBean implements InitializingBean, DisposableBean { public void afterPropertiesSet() { // 初始化逻辑 } public void destroy() { // 销毁逻辑 } } ``` 3. **自动装配模式**: - byType:根据类型匹配 - byName:根据属性名称匹配 - constructor:构造器参数匹配 --- ### 八、最佳实践建议 1. **推荐使用构造器注入**,保证依赖不可变 2. **避免循环依赖**(A依赖B,B又依赖A) 3. **合理使用延迟加载**(@Lazy) 4. **结合@ComponentScan自动发现组件**: ```java @Configuration @ComponentScan("com.example") public class AppConfig {} ``` --- **思考题**:假设存在两个实现`UserRepository`接口的Bean: - MySQLUserRepository(主库) - BackupUserRepository(备用库) 当使用`@Autowired`注入时,容器会如何处理?如何指定使用特定实现?(提示:使用@Qualifier或@Primary注解)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java程序员廖志伟

赏我包辣条呗

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值