Spring的循环依赖
循环依赖:一个对象或多个对象之间存在直接或间接的依赖关系,这种关系构成了一种循环调用。
- 自己依赖自己的直接依赖
- 两个对象之间的直接依赖
- 多个对象之间的间接依赖
循环依赖的N种场景
- 单例的setter注入(能解决)
- 多例的setter注入(不能解决)
- 构造器注入(不能解决)
- 单例的代理对象setter注入(有可能解决)
- DependsOn循环依赖(不能解决)
第一种情况,直接通过@Autowired直接注入的循环依赖Spring利用内部的三级缓存已解决
spring内部有三级缓存:DefaultSingletonBeanRegistry
- singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例
- earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例(此时还未填充属性)
- singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象
spring解决循环依赖的方式
bean1=>一级缓存中获取不到,创建实例=>提前暴露,添加到三级缓存=>依赖注入bean2=>一级缓存中获取不到,创建实例=>提前暴露,添加到三级缓存
=>依赖注入bean1=>三级缓存中获取到实例,并添加到二级缓存=>bean2的依赖注入成功,bean2初始化成功,bean2添加到一级缓存
=>bean1的依赖注入成功,bean1初始化成功,bean1添加到一级缓存=>结束
第二种情况,多例的注入,使用@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)将bean声明为多例的类
此时程序能够正常启动,非单例不会被提前初始化(非抽象、单例 并且非懒加载的类才能被提前初始bean)
这种循环依赖问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。
第三种情况,构造器注入
此种情况在创建实例的时候便进行了对应依赖的创建,无法利用到缓存,也无法解决循环依赖
第四种情况,单例的代理对象setter注入,@Async注解的场景,会通过AOP自动生成代理对象
如图一的情形,bean初始化完成之后,后面还有一步去检查:第二级缓存 和 原始对象 是否相等,如不相等便抛出循环依赖异常
默认情况下,spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载,由于加载顺序的不一致,有可能不会出现循环依赖
对于A中注入B的方式为setter方法,B中注入A的方式为构造器的循环依赖:如果A先加载则可以解决循环依赖,B先加载则不能;依据自然排序的加载顺序加载
第五种情况,使用@DependsOn注解会出现循环依赖
AbstractBeanFactory类的doGetBean会检查dependsOn的实例有没有循环依赖,如果有循环依赖则抛异常
解决循环依赖的方法:
- 使用@Lazy注解,延迟加载(构造器循环依赖)
- 使用@DependsOn注解,指定加载先后关系
- 修改文件名称,改变循环依赖的加载顺序(单例的代理对象setter注入的循环依赖)