本文主要是分析spring bean的循环依赖,以及spring的解决方式。 通过这种解决方式,我们可以应用在我们实际开发项目中。
1. 什么是循环依赖?
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如a依赖于b,b依赖于c,c又依赖于a。如下图:
注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。
spring中循环依赖场景有:
(1)构造器的循环依赖
(2)field属性的循环依赖。
循环依赖的产生和解决的前提
循环依赖的产生可能有很多种情况,例如:
- a的构造方法中依赖了b的实例对象,同时b的构造方法中依赖了a的实例对象
- a的构造方法中依赖了b的实例对象,同时b的某个field或者setter需要a的实例对象,以及反之
- a的某个field或者setter依赖了b的实例对象,同时b的某个field或者setter依赖了a的实例对象,以及反之
当然,spring对于循环依赖的解决不是无条件的,首先前提条件是针对scope单例并且没有显式指明不需要解决循环依赖的对象,而且要求该对象没有被代理过。同时spring解决循环依赖也不是万能,以上三种情况只能解决两种,第一种在构造方法中相互依赖的情况spring也无力回天。结论先给在这,下面来看看spring的解决方法,知道了解决方案就能明白为啥第一种情况无法解决了。
2. 怎么检测是否存在循环依赖
检测循环依赖相对比较容易,bean在创建的时候可以给该bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。
3. spring怎么解决循环依赖
spring的循环依赖的理论依据其实是基于java的引用传递,当我们获取到对象的引用时,对象的field或则属性是可以延后设置的(但是构造器必须是在获取引用之前)。
spring的单例对象的初始化主要分为三步:
(1)createbeaninstance:实例化,其实也就是调用对象的构造方法实例化对象
(2)populatebean:填充属性,这一步主要是多bean的依赖属性进行填充
(3)initializebean:调用spring xml中的init 方法。
从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二部。也就是构造器循环依赖和field循环依赖。
那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在cache中,spring为了解决单例的循环依赖问题,使用了三级缓存。
首先我们看源码,三级缓存主要指:
1
2
3
4
5
6
7
8
|
/** cache of singleton objects: bean name --> bean instance */
private final map<string, object> singletonobjects = new concurrenthashmap<string, object>( 256 );
/** cache of singleton factories: bean name --> objectfactory */
private final map<string, objectfactory<?>> singletonfactories = new hashmap<string, objectfactory<?>>( 16 );
/** cache of early singleton objects: bean name --> bean instance */
private final map<string, object> earlysingletonobjects = new hashmap<string, object>( 16 );
|
这三级缓存分别指:
- singletonfactories : 单例对象工厂的cache
- earlysingletonobjects :提前暴光的单例对象的cache
- singletonobjects:单例对象的cache
我们在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonobjects。主要调用方法就就是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
protected object getsingleton(string beanname, boolean allowearlyreference) {
object singletonobject = this .singletonobjects.get(beanname);
if (singletonobject == null && issingletoncurrentlyincreation(beanname)) {
synchronized ( this .singletonobjects) {
singletonobject = this .earlysingletonobjects.get(beanname);
if (singletonobject == null && allowearlyreference) {
objectfactory<?> singletonfactory = this .singletonfactories.get(beanname);
if (singletonfactory != null ) {
singletonobject = singletonfactory.getobject();
this .earlysingletonobjects.put(beanname, singletonobject);
this .singletonfactories.remove(beanname);
}
}
}
}
return (singletonobject != null_object ? singletonobject : null );
}
|
上面的代码需要解释两个参数:
- issingletoncurrentlyincreation()判断当前单例bean是否正在创建中,也就是没有初始化完成(比如a的构造器依赖了b对象所以得先去创建b对象, 或则在a的populatebean过程中依赖了b对象,得先去创建b对象,这时的a就是处于创建中的状态。)
- allowearlyreference 是否允许从singletonfactories中通过getobject拿到对象
分析getsingleton()的整个过程,spring首先从一级缓存singletonobjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlysingletonobjects中获取。如果还是获取不到且允许singletonfactories通过getobject()获取,就从三级缓存singletonfactory.getobject()(三级缓存)获取,如果获取到了则:
1
2
|
this .earlysingletonobjects.put(beanname, singletonobject);
this .singletonfactories.remove(beanname);
|
从singletonfactories中移除,并放入earlysingletonobjects中。其实也就是从三级缓存移动到了二级缓存。
从上面三级缓存的分析,我们可以知道,spring解决循环依赖的诀窍就在于singletonfactories这个三级cache。这个cache的类型是objectfactory,定义如下:
1
2
3
|
public interface objectfactory<t> {
t getobject() throws beansexception;
}
|
这个接口在下面被引用
1
2
3
4
5
6
7
8
9
10
|
protected void addsingletonfactory(string beanname, objectfactory<?> singletonfactory) {
assert .notnull(singletonfactory, "singleton factory must not be null" );
synchronized ( this .singletonobjects) {
if (! this .singletonobjects.containskey(beanname)) {
this .singletonfactories.put(beanname, singletonfactory);
this .earlysingletonobjects.remove(beanname);
this .registeredsingletons.add(beanname);
}
}
}
|
这里就是解决循环依赖的关键,这段代码发生在createbeaninstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以spring此时将这个对象提前曝光出来让大家认识,让大家使用。
这样做有什么好处呢?让我们来分析一下“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通过objectfactory将自己提前曝光了,所以b能够通过objectfactory.getobject拿到a对象(虽然a还没有初始化完全,但是总比没有好呀),b拿到a对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonobjects中。此时返回a中,a此时能拿到b的对象顺利完成自己的初始化阶段2、3,最终a也完成了初始化,进去了一级缓存singletonobjects中,而且更加幸运的是,由于b拿到了a的对象引用,所以b现在hold住的a对象完成了初始化。
知道了这个原理时候,肯定就知道为啥spring不能解决“a的构造方法中依赖了b的实例对象,同时b的构造方法中依赖了a的实例对象”这类问题了!因为加入singletonfactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/u010853261/article/details/77940767