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

Spring中三级缓存如何解决循环依赖的

Spring中是如何解决循环依赖的

前言

我们都知道,Spring时使用了三级缓存来解决的循环依赖问题。但是三级缓存是什么?三级缓存又是如何解决循环依赖的呢?为什么需要三级缓存,二级不行吗?带着这些问题,我们一起来研究一下Spring的源码,揭秘上面的问题。

SpringBean的生命周期

要说要Spring的循环依赖,首先要说一下SpringBean的生命周期,因为Spring的循环依赖的发生和解决就是出现在SpringBean的生命周期中,只有了解的SpringBean的生命周期,我们才能更清楚地理解循环依赖。

SpringBean的声明周期大致如下:

  1. 扫描配置文件,获取BeanDefination定义
  2. 通过反射Reflect API的方式创建Bean对象
  3. 对Bean对象进行属性填充,populateBean()
  4. 如果Bean实现了BeanNameAware接口,则调用setBeanName()传入Bean的name。
  5. 如果Bean实现了BeanClassLoader接口,则调用setBeanClassLoader(),传入classLoad对象。
  6. 同上相应的如果实现了*Aware接口,则调用相应的set方法。
  7. 如果有加载这个Bean的Spring容器相关的postprocessor对象,则执行postProcessBeforeInitialization()初始化前方法
  8. 如果Bean实现了initializeBean方法,则调用afterPropertiesSet()方法
  9. 如果Bean在配置文件中定义包含init-method属性,则执行相应的方法。
  10. 如果有加载这个Bean的Spring容器相关的postprocessor对象,则执行postProcessAfterInitialization()初始化前方法
  11. 销毁Bean的时候,如果Bean实现了DisposableBean接口,则执行destory()方法。
  12. 销毁Bean的时候,如果Bean在配置文件中配置了destory-method属性,则执行指定的方法。

简单说说Spring Bean的生命周期

什么是循环依赖

假设我们有两个类 A 和 B,类A和类B的实例互为成员变量即为循环依赖。简单来说,就是A依赖B,而B也依赖A。这就构成了循环依赖。

publicclassA{privateBb;publicBgetB(){returnb;}publicvoidsetB(Bb){this.b=b;}}publicclassB{privateAa;publicAgetA(){returna;}publicvoidsetA(Aa){this.a=a;}}

循环依赖配置

<?xml version="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!-- <bean id="userService" class="com.spring.cycle.UserService">--><!-- <property name="orderService" ref="orderService"/>--><!-- </bean>--><!-- <bean id="orderService" class="com.spring.cycle.OrderService">--><!-- <property name="userService" ref="userService"/>--><!-- </bean>--><beanid="a"class="com.spring.cycle.A"><propertyname="b"ref="b"/></bean><beanid="b"class="com.spring.cycle.B"><propertyname="a"ref="a"/></bean><!--配置客户service--><!-- <bean id="customerService" class="com.spring.cycle.CustomerServiceImpl">--><!-- </bean>--><!-- <bean id="logger" class="com.spring.cycle.Logger">--><!-- </bean>--><!-- &lt;!&ndash;第一步:通过aop:config声明aop的配置&ndash;&gt;--><!-- <aop:config>--><!-- &lt;!&ndash;第二步:通过aop:aspect配置切面,说明:--><!-- id:唯一标识名称--><!-- ref:指定通知bean对象的引用--><!-- &ndash;&gt;--><!-- <aop:aspect id="logger" ref="logger">--><!-- &lt;!&ndash;第四步:通过aop:pointcut配置切入点表达式,说明:--><!-- id:唯一标识名称--><!-- expression:指定切入点表达式--><!-- 切入点表达式组成:--><!-- 访问修饰符 返回值 包名称 类名称 方法名称 (参数列表)--><!-- &ndash;&gt;--><!-- <aop:pointcut id="pt1" expression="execution(public void com.spring.cycle.CustomerServiceImpl.saveCustomer())"/>--><!-- &lt;!&ndash;第三步:通过aop:before配置前置通知,说明:--><!-- method:指定通知方法名称--><!-- pointcut-ref:指定切入点表达式--><!-- &ndash;&gt;--><!-- <aop:before method="printLog" pointcut-ref="pt1"/>--><!-- </aop:aspect>--><!-- </aop:config>--></beans>

Spring的三级缓存是什么

简单点说,Spring的三级缓存其实就是三个Map,分别用来存储不同阶段的Spring的Bean。

/** 一级缓存,用来存储完整的Bean对象。key: Bean的名称,value: Bean的实例 */privatefinalMap<String,Object>singletonObjects=newConcurrentHashMap<>(256);/** 三级缓存,存储的是一段lambda表达式,不会直接运行,在调用getObject()方法时才会运行 key: Bean的名称,value: 生成对象的工厂*/privatefinalMap<String,ObjectFactory<?>>singletonFactories=newHashMap<>(16);/** 二级缓存,用来存提前暴露的Bean对象 key: Bean的名称,value: Bean的早期实例 */privatefinalMap<String,Object>earlySingletonObjects=newConcurrentHashMap<>(16);

循环依赖是如何发生的

大体流程是这样的:

  1. 通过扫描配置文件,需要创建A对象
  2. 去一级缓存中获取A,没有A则创建A,createBeanInstance
  3. 把A对象的半成品放到了缓存中
  4. 实例化A对象之后,对A对象进行属性填充,populateBean,发现A对象依赖B对象
  5. 于是去缓存池中找B对象,发现不存在,于是创建B对象,createBeanInstance
  6. 把B对象的半成品放到了缓存中
  7. 实例化B对象之后,对B对象进行属性填充,populateBean,发现B对象依赖A对象
  8. 在二级缓存中找到A对象的半成品,B对象完成属性填充
  9. 初始化B对象initializeBean,B对象完成创建,把B对象放入到一级缓存中去,清空二级缓存
  10. 然后A对象可以在一级缓存中获取B对象,完成属性填充
  11. 初始化A对象initializeBean,A对象创建完成,把A对象加入到一级缓存中,清除二级缓存

加入到一级缓存后清除二级缓存是为了保证对象只有一个实例。

为什么需要三级缓存

从上面的分析我们可以看出,解决循环依赖使用一级缓存 + 二级缓存就可以了,为什么还要三级缓存呢?

大家想一下,有这么一种情况,就是我们给A对象添加了代理AOP,在B对象进行属性填充的时候,需要注入的应该是A的代理对象,从上面的流程中可以看出,使用二级缓存并不能满足。

其实也不能说是不能满足,只要让A对象提前AOP就好了,这样B对象在进行属性填充的时候,注入的就是A的代理对象了。

但是Spring的原则是在对象初始化之后,initializeBean之后,postProcessAfterInitialization中,进行AOP的。而且,也并不是每个Bean都需要进行AOP的。

所以Spring的选择是,在Bean实例化之后,将Bean包装成一个ObjectFactory放到了三级缓存singletonFactories中;

大致步骤如下:

  1. 通过扫描配置文件,需要创建A对象
  2. 去一级缓存中获取A,一级缓存中没有A,去二级缓存中查找A,二级缓存中没有A,去三级缓存中查找A,没有A则创建A,createBeanInstance
  3. 把A对象的半成品ObjectFactory放到了三级缓存中
  4. 实例化A对象之后,对A对象进行属性填充,populateBean,发现A对象依赖B对象
  5. 去一级缓存中获取B,一级缓存中没有B,去二级缓存中查找B,二级缓存中没有B,去三级缓存中查找B,没有B则创建B,createBeanInstance
  6. 把B对象的半成品ObjectFactory放到了三级缓存中
  7. 实例化B对象之后,对B对象进行属性填充,populateBean,发现B对象依赖A对象
  8. 去一级缓存中获取A,一级缓存中没有A,去二级缓存中查找A,二级缓存中没有A,三级缓存中查找A,A存在三级缓存中,获取A对象,B对象完成属性填充,删除三级缓存中的A,把A添加到二级缓存中去(为了保证只有一个实例对象)
  9. 初始化B对象initializeBean,B对象完成创建,把B对象放入到一级缓存中去,清空三级缓存,二级缓存中的B
  10. 然后A对象可以在一级缓存中获取B对象,完成属性填充
  11. 初始化A对象initializeBean,A对象创建完成,把A对象加入到一级缓存中,清除二级缓存

在出现AOP时:

  • DefaultSingletonBeanRegistry

  • AbstractAutowireCapableBeanFactory

  • AbstractAutowireCapableBeanFactory

  • AbstractAutoProxyCreator

在出现AOP的循环依赖时

拿到 ObjectFactory 对象后,调用 ObjectFactory.getObject() 方法最终会调用 getEarlyBeanReference() 方法。

getEarlyBeanReference 这个方法主要逻辑大概描述下如果 bean 被 AOP 切面代理则返回的是 beanProxy 对象,如果未被代理则返回的是原 bean 实例。

这时我们会发现能够拿到 bean 实例(属性未填充),然后从三级缓存移除,放到二级缓存 earlySingletonObjects 中,而此时 B 注入的是一个半成品的实例 A 对象。

不过随着 B 初始化完成后,A 会继续进行后续的初始化操作,最终 B 会注入的是一个完整的 A 实例,因为在内存中它们是同一个对象。

总结

Spring是利用三级缓存解决循环依赖的。通过提前暴露对象,将把成品的对象放入到缓存中,完成循环依赖中对象的属性注入。

在没有AOP的情况下,二级缓存就可以解决循环依赖。

但是在AOP的循环依赖中,则需要三级缓存来解决循环依赖。因为Spring的原则是初始化之后再进行对象的增强。如果只有二级缓存,则必须提前AOP,再实例化之后就对对象进行AOP增强。也并非所有的都对象都需要进行提前的AOP,使用三级缓存解决了这个问题。

在出现AOP的循环依赖时

拿到 ObjectFactory 对象后,调用 ObjectFactory.getObject() 方法最终会调用 getEarlyBeanReference() 方法。

getEarlyBeanReference 这个方法主要逻辑大概描述下如果 bean 被 AOP 切面代理则返回的是 beanProxy 对象,如果未被代理则返回的是原 bean 实例。

http://www.jsqmd.com/news/1194300/

相关文章:

  • 长沙芙蓉区香奈儿古驰回收避坑,出售奢侈品优选即时结算实体商家 - 逸程奢侈品回收中心
  • 2026年电线电缆挤出机、高速束丝机、吹水器设备5家深度实测,源头工厂避坑指南 - 中国远见品牌企业资讯
  • BLHeli_32高级配置方案:专业级电调固件性能调优与硬件调试指南
  • fuck-coding-interviews中的排序算法实战:从冒泡排序到快速排序
  • 终极指南:如何在Windows电脑上直接安装安卓应用?APK安装器完整教程
  • 5个实用 wd 使用场景:从项目开发到日常工作的效率提升
  • BG3ModManager:高效管理博德之门3模组的专业解决方案
  • 2026 济南黄金回收避坑攻略 正规实体门店实地测评榜单 - 资讯速览
  • 常州天宁区黄金存放避坑 + 正规回收门店实测|梅雨季闲置黄金保养变现全指南 - 铂衡汇黄金珠宝
  • 从ToC到ToB全场景覆盖:Jiuwen AgentCore如何满足不同行业AI智能体需求
  • SingGuard-4b-GGUF实战教程:文本内容安全检测的10个应用场景
  • 标准成本、实际成本、变动成本,别再混用!一文讲透制造成本核算
  • Wand-Enhancer:免费解锁WeMod专业版的完整本地增强指南
  • Ascend C SIMD-API Layout函数
  • 2026成都两联供系统服务商避坑参考:施工细节全面解析 - 信息热点
  • 从名字读懂云克隆:一家用二十年“打磨”的科研试剂生产企业
  • Navicat 16/17 Mac版无限试用期重置终极指南:三种方法轻松实现永久免费使用
  • 如何通过HowToCookOnMiniprogram学习模块快速提升烹饪技巧
  • 重庆积家中国官方售后服务体系全解析 | 官方网站权威认证(2026 年 7 月最新) - 积家中国服务中心
  • 终极解决方案:DsHidMini让你的PS3手柄在Windows上完美重生
  • 彻底解决UFO项目Follower模式提前终止问题:从根源到方案
  • awesome-text-to-image-studies中的扩散变换器(DiT)革命:从PixArt到Stable Diffusion 3
  • 如何快速掌握免费图表工具:draw.io桌面版完整使用指南
  • 2026川藏纯玩团怎么选?经济/舒适/豪华三档预算对比? - 老金2026
  • 艾尔登法环存档编辑器:3步解锁游戏无限可能,告别重复刷图的烦恼!
  • 小程序计算机毕设之基于 SpringBoot+Android 的校园树洞论坛服务系统 基于 SpringBoot的校园话题讨论管理系统的设计与实现(完整前后端代码+说明文档+LW,调试定制等)
  • Windows 10 PL-2303驱动终极兼容指南:告别代码10错误
  • 2026秦皇岛高价回收名表靠谱商家 素君奢品汇13111597382 高价回收可上门 - GrowUME
  • 2026专业GEO优化服务推荐:B2B获客领域代表性服务商解析 - 信息热点
  • Ascend C卷积反传权重设置