欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-02-24 10:24:16
本文主要是分析spring bean的循环依赖,以及spring的解决方式。 通过这种解决方式,我们可以应用在我们实际开发项目中。 1. 什么是循环依赖? 循环依赖其实...

本文主要是分析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为了解决单例的循环依赖问题,使用了三级缓存。

首先我们看源码,三级缓存主要指:

/** 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。主要调用方法就就是:

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()(三级缓存)获取,如果获取到了则:

this.earlysingletonobjects.put(beanname, singletonobject);
            this.singletonfactories.remove(beanname);

从singletonfactories中移除,并放入earlysingletonobjects中。其实也就是从三级缓存移动到了二级缓存。

从上面三级缓存的分析,我们可以知道,spring解决循环依赖的诀窍就在于singletonfactories这个三级cache。这个cache的类型是objectfactory,定义如下:

public interface objectfactory<t> {
  t getobject() throws beansexception;
}

这个接口在下面被引用

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三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。