详解Spring-bean的循环依赖以及解决方式

时间:2022-09-25 13:33:40

本文主要是分析spring bean循环依赖,以及spring的解决方式。 通过这种解决方式,我们可以应用在我们实际开发项目中。

1. 什么是循环依赖?

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如a依赖于b,b依赖于c,c又依赖于a。如下图:

详解Spring-bean的循环依赖以及解决方式

注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。

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的单例对象的初始化主要分为三步:

详解Spring-bean的循环依赖以及解决方式

(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);
}

上面的代码需要解释两个参数:

  1. issingletoncurrentlyincreation()判断当前单例bean是否正在创建中,也就是没有初始化完成(比如a的构造器依赖了b对象所以得先去创建b对象, 或则在a的populatebean过程中依赖了b对象,得先去创建b对象,这时的a就是处于创建中的状态。)
  2. 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