Spring中的循环依赖问题
1、什么是Spring中的循环依赖
循环依赖就是循环引用,就是两个或多个bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环
循环依赖的Error演示
Spring中分为两种情况
1)构造器循环依赖、
此依赖是无法解决的,只能抛出BeanCurrentlyIn ,CreationException异常表示循环依赖。如在创建TestA类时,构造器需要TestB类,那将去创建TestB,在创建TestB类时又发现需要TestC类,则又去创建TestC,最终在创建TestC时发现又需要TestA,从而形成一个环,没办法创建。
Spring容器将每一个正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中将一直保持在这个池中,因此如果在创建bean过程中发现自己已经在“当前创建bean池”里时,将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的bean将从“当前创建bean池”中清除掉
通过原生JavaSE代码演示:
2)setter循环依赖
对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的bean来完成的,而且只能解决单例作用域的bean循环依赖。
我们都知道Spring中Bean的作用域(scope)默认是Single,就是说在整个bean中他们共享的是一个对象,理论来说是不会出现循环依赖的:
- 第一种默认(Singleton):
DemoA:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** * @author zhangzhixi * @date 2021-6-4 19:45 */ public class DemoA { private DemoB demoB; public DemoB getDemoB() { return demoB; } public void setDemoB(DemoB demoB) { this .demoB = demoB; } } |
DemoB:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** * @author zhangzhixi * @date 2021-6-4 19:45 */ public class DemoB { private DemoA demoA; public DemoA getDemoA() { return demoA; } public void setDemoA(DemoA demoA) { this .demoA = demoA; } } |
Bean:
测试:(正常)
1 2 3 4 5 6 | @Test public void test1() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "application.xml" ); DemoA a = applicationContext.getBean( "a" , DemoA. class ); DemoB b = applicationContext.getBean( "b" , DemoB. class ); } |
- 第二种原型:(protype):
修改bean的作用域为:protype,我们再进行一个测试~
报错:
2、怎么解决Spring中的循环依赖
Spring三级缓存:
只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每次从容器中获取的都是一个新的对象,都会重新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中。
第一级(单例池):singletonObjects,存放已经经历了完整生命周期的bean对象。
第二级:earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充)。
第三级:Map<String, ObjectFactory<?>> singletonFactories,存放可以生成Bean的工厂。
A/B两个互相依赖的对象在三级缓存中迁移说明?
循环依赖源码看完了尚硅谷周阳讲的,实在是太高深了,下面放个图(精简版):
总结Spring如何解决循环依赖的:
Spring解决循环依赖依靠的是Bean“中间态”的概念,即已经实例化但是还未初始化的状态。实例化过程是通过构造器实现的,如果A还未创建好则不能提前暴露,所以构造器注入无法解决循环依赖问题。
Spring解决循环依赖过程:
- 调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA
- 在getSingleton()方法中,从【一级缓存】中查找,没有,返回null
- doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)
- 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法
- 进入AbstractAutowireCapableBeanFactory#doCreateBean,先利用反射创建出beanA的实例,然后判断:是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中(即是否在第四步的集合中)。判断为true则将beanA添加到【三级缓存】中
- 对beanA进行属性填充(populateBean),此时检测到beanA依赖于beanB,于是开始查找beanB
- 调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则先在【三级缓存】中创建,然后给beanB填充属性
- 此时 beanB依赖于beanA,调用getSingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从【三级缓存】中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA
- 将beanA从【三级缓存】移动到【二级缓存】,beanB就获取到了beanA的依赖,beanB顺利完成实例化,从【三级缓存】直接移动到【一级缓存】
- 随后beanA继续属性填充工作,从【一级缓存】中获取到beanB,beanA完成初始化,回到getsingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话