Spring怎么解决循环依赖?
循环依赖
循环依赖是个啥?在创建TestA类时,构需要TestB类,那将去创建TestB,在创建TestB类时又需要TestA,从而形成一个环,没办法创建。
spring解决这个问题与三特别有缘分,对于三种不同程度的循环依赖,spring能解决的程度不同;而解决的办法正是用到了三级缓存。
下面就分别来讨论三种不同程度的循环依赖。
第一种:构造器的循环依赖,无法解决,会报错
情况
A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象
无法解决原因
一、Spring容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
Spring容器先创建单例StudentA,StudentA依赖StudentB,然后将A放在“当前创建Bean池”中,此时创建StudentB,StudentB依赖StudentA, 但是,此时StudentA已经在池中,所以会报错,因为在池中的Bean都是未初始化完的,所以会依赖错误 ,(初始化完的Bean会从池中移除)
二、循环依赖发生的时候还没有执行完构造器,也就无法使用三级缓存解决问题。spring解决循环依赖的手段是三级缓存,而三级缓存的的前提是执行了构造器,才会调用addSingletonFactory加入三级缓存,所以构造器的循环依赖没法解决。这在下面一种情况中会有解释,就先不展开了。
第二种:field属性的单例循环依赖,可解决。
情况
A的构造方法中依赖了B的实例对象,同时B的某个field或者setter需要A的实例对象
解决前提
- 前提一:比第一种情况,此时循环依赖发生在字段属性中,而不是构造器中。
- 前提二:比第三种情况,因为是单例的,所以才会用到三级缓存。
前提一:是先实例化在设置对象属性的,否则解决field属性的setter方式单例循环依赖将无从谈起。先来看看spring bean生命周期的一张图。
大体的流程就是:实例化>填充属性>initializeBean(调用spring xml中的init 方法)
循环依赖主要发生在实例化>填充属性这两步。
前提二:对于单例来说,在Spring容器会把对象放在缓存中,而为了解决单例的循环依赖问题,使用了Spring三级缓存。
解决手段
三级缓存,这三级缓存分别指:
- singletonObjects:单例对象的cache,一级缓存。
- earlySingletonObjects :提前暴光的单例对象的Cache ,二级缓存。
- singletonFactories : 单例对象工厂的cache ,三级缓存。
由上往下,缓存中的bean是逐渐完善的,三级缓存中的bean 是最初始的状态。
解决过程
“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。
A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories三级缓存中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程。
B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A提前曝光了,所以B能够通过三级缓存singletonFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀)。
B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中。
而且由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。
源码
getSingleton()
获取单例的方法,逐层从缓存中取
1 | protected Object getSingleton(String beanName, boolean allowEarlyReference) { |
addSingletonFactory()
这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。
注意:这里有一个细节就是ObjectFactory,三级缓存的类型是ObjectFactory并非一个bean(还没实例化好呢)。
1 | protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { |
知道了这个原理时候,肯定就知道为啥Spring不能解决第一种情况“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”。因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
第三种:field属性的prototype循环依赖,无法解决解决,报错
情况
A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象。
不能解决的前提
因为没有缓存。
scope=”prototype” 意思是 每次请求都会创建一个实例对象。两者的区别是:有状态的bean都使用Prototype作用域,无状态的一般都使用singleton单例作用域。
对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。