type
Post
status
Published
date
Aug 8, 2024
slug
summary
Spring循环依赖时如何解决的?
tags
Spring
技术
category
技术分享
icon
password
本文目的
- Spring循环依赖是什么?
- Spring循环依赖是怎么出现的?
- Spring循环依赖怎么解决呢?
说明
Spring的循环依赖,顾名思义相互之间有依赖,和鸡生蛋蛋生鸡一样。所以本文要分析它是怎么出现的,该怎么解决
Spring循环依赖是什么?
也称为循环引用,是指两个或多个Bean之间相互依赖,形成一个环状依赖链。如:A依赖B,B依赖A
Spring循环依赖是怎么出现的?
从业务上看就是多个对象之间相互依赖,不知道谁应先创建好再创建下一个。从正常数学规律来看像阿拉伯数字一样1之后是2再3,一个接一个出现,很明显这是违背正常规律的所以也有人认为这是设计不合理,但系统一直在演进总会有些因为迎合需求或者追求快而舍弃合理设计。虽然重新拆分优化从业务逻辑解决,但往往成本很高,所以这就需要一种折中解决方案允许它存在,且不影响原有功能。我想Spring解决循环依赖也是综合这方面的考虑吧。
Spring循环依赖怎么解决呢?
从依赖关系看就是A依赖B,B依赖A,它们都是唯一的导致创建A时又要B才算完成,而创建B又要A才算完成进入环形链,无法结束。但这个的前提是A和B是唯一的,如果不是唯一的自然就打破了环的形成。(例子:需要A时创建A而A依赖B,B就创建B1,B1需要A则创建A1,反之也一样,A和B1依赖的A(A1)不是一个对象实例,每次获取都是新建,所以不会维护唯一性直接创建就行,因此也不需要缓存已创建实例和锁来维护唯一性,简单理解是就是业务逻辑原本是递归获取依赖,而没有完成实例化的不会放进缓存(只有一级)就死循环,现在获取依赖直接new+初始化)。 所以第一种解决方案就是使用原先型(Spring中Bean的Scope=prototype)
其实Spring自己在针对创建单例Bean的时候就有解决循环依赖,而它的方案是三级缓存,例如A依赖B,B依赖A,创建A时因为要创建B这时A是不完整的但为了创建B时知道A会创建出来所以创建A时提前暴露出来,让B创建时能获取到正在创建的A,从这里看是只需要两个缓存,那为什么Spring要用三级缓存?结合源码个人理解是,创建A时它自己不知道和谁会形成循环依赖所以默认需要提前暴露出来,那我已经new出来直接放二级缓存,B不也能获取到A了吗?这又涉及到代理类,因为A提前暴露的是刚new出来的还未检查是否需要代理,如果A需要代理放入二级缓存需要先产生代理类这也是可以的,但之所以引入三级缓存,从代码逻辑看从三级缓存获取Bean和正常非循环依赖生成Bean是低耦合的,且延迟在需要时才从三级缓存拿出来初始化,所以三级缓存是与正常逻辑解耦,是一种拓展设计,从另一个角度(生命周期)看因为调用了BeanPostProcessor接口实现类(后处理器),如果没用到就是直接初始化且调用后处理,调用顺序和生命周期相违背。 所以第二种解法是Spring自身的兼容但是它是有限制的通过注解和Setter方法注入是可以正常创建Bean,而构造器注入部分可以部分不可以, 如: a、A依赖B,是用注解/setter注入的,B依赖A是构造器注入的可以正常创建Bean; b、如果A、B都是构造器注入则不行; c、A依赖B是构造器注入,B依赖A时Setter注入也不行 a情况之所以可以是因为A比B先创建(Spring创建Bean和名字排序和名字有关,ASCII顺序)创建A时能用无参构造器反射创建,而b、c情况则需要有依赖值的有参构造器,且依赖值这时无法获取。
第三种方式是懒加载使用@Lazy注解(属性、setter方法、构造器都可)标识依赖注入是懒加载的,在给属性赋值时检查到时懒加载就会封装一个懒加载代理对象
总结
出现的原因:
Spring循环依赖因为对象之间相互依赖,形成环,且对象实例是单例时就会产生循环依赖。
解决方案有三种:
1、依赖对象实例是原型,每次获取都是不同的实例
2、注入方式,相互依赖之间使用属性+注解方式注入或者Setter方法注入,不要用构造器注入,虽然部分情况可以但是会因为创建顺序而导致部分不可以正常创建Bean
3、使用懒加载,@Lazy注解(属性、setter方法、构造器都可)
有关使用上的问题,欢迎您在底部评论区留言,一起交流~
- 作者:chenyou
- 链接:https://blog.chenyou.top/article/385fdc54-4857-453d-a5ca-7a5176b56ad8
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

