spring如何解决循环依赖问题

一、循环依赖

一般来讲,我们说的循环依赖,指得是2个或两个以上的bean 互相持有对方,最终形成闭环的情况。

比如A依赖B,B依赖C,C又依赖A。

spring如何解决循环依赖问题_第1张图片

自已依赖自己,也会发生循环依赖的问题。

spring如何解决循环依赖问题_第2张图片

二、Spring中什么时候会发生循环依赖

我们首先要知道,Spring的单例对象的初始化主要分为三步:
spring如何解决循环依赖问题_第3张图片

  • createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
  • populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
  • initializeBean:调用spring xml中的init 方法。

从上面单例bean的初始化可以知道:循环依赖主要发生在第一、二步,也就是构造器循环依赖field循环依赖。那么我们要解决循环引用也应该从初始化过程着手。

注: 为什么说单例对象的初始化。这是因为原型(Prototype)的场景是不支持循环依赖的,通常会走到AbstractBeanFactory类中下面的判断,抛出异常。

if (isPrototypeCurrentlyInCreation(beanName)) {
     
  throw new BeanCurrentlyInCreationException(beanName);
}

所以默认的是单例的属性注入。

(1)使用构造器进行依赖注入的时候 (以两个bean为例)

public class StudentA {
       
  
    private StudentB studentB ;  
  
    public void setStudentB(StudentB studentB) {
       
        this.studentB = studentB;  
    }  
  
    public StudentA() {
       
    }  
      
    public StudentA(StudentB studentB) {
       
        this.studentB = studentB;  
    }  
}  
public class StudentB {
       
  
    private StudentA studentA ;  
  
    public void setStudentC(StudentA studentA) {
       
        this.studentA = studentA;  
    }  
      
    public StudentB() {
       
    }  
  
    public StudentB(StudentA studentA) {
       
        this.studentA = studentA;  
    }  
}  
<bean id="a" class="com.zfx.student.StudentA">  
    <constructor-arg index="0" ref="b">constructor-arg>  
bean>  

<bean id="b" class="com.zfx.student.StudentB">  
    <constructor-arg index="0" ref="a">constructor-arg>  

执行结果:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:   
    Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference? 

这种循环依赖spring没有办法解决,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。

源码:
spring如何解决循环依赖问题_第4张图片
源码分析:

Spring容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常来表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。

以上面的程序为例,Spring容器先创建单例A,但是A依赖B,所以将A放在
“当前创建Bean池”中,此时创建B,发现B依赖A, 但是,此时A已经在池中,所以会报错。因为在池中的Bean都是未初始化完的(正在创建的),所以会产生依赖错误 ,(初始化完的Bean会从池中移除)。

(2)使用setter方法进行依赖注入的时候

spring如何解决循环依赖问题_第5张图片
采用三级缓存解决。

结合上图来看,Spring先是构造实例化Bean对象 ,创建成功后,Spring会通过代码提前将对象暴露出来,此时的对象A还没有完成属性注入,属于早期对象,此时Spring会将这个实例化结束的对象放到一个Map中,并且Spring提供了获取这个未设置属性的实例化对象引用的方法。 结合我们的实例来看,当Spring实例化了A、B后,紧接着会去设置对象的属性,此时A依赖B,就会去Map中取出存在里面的单例B对象,以此类推,不会出来循环的问题。

换句话说,对于setter注入造成的依赖是通过Spring容器提前暴露刚完成实例化但未完成初始化的bean来解决的,而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean。

三、解决循环依赖

spring如何解决循环依赖问题_第6张图片

对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。

我们首先看源码,三级缓存主要指:
spring如何解决循环依赖问题_第7张图片
这三级缓存分别指:

  • singletonObjects:单例对象的cache,存放可用的成品Bean。(一级缓存)
  • earlySingletonObjects:提前暴光的单例对象的cache,存放半成品的Bean,半成品的Bean是已创建对象,但是未注入属性和初始化,用以解决循环依赖。(二级缓存)
  • singletonFactories : 单例对象工厂的cache,存的是Bean工厂对象,用来生成半成品的Bean并放入到二级缓存中,用以解决循环依赖。(三级缓存)

我们在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。

获取bean:
spring如何解决循环依赖问题_第8张图片
主要调用**getSingleton()**方法来获取bean;
spring如何解决循环依赖问题_第9张图片
上面的代码需要解释两个参数:

  • isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化完成(比如A的构造器依赖了B对象所以得先去创建B对象,或则在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。)
  • allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象。

分析getSingleton()的整个过程:Spring首先从一级缓存singletonObjects中获取获取这个单例的bean对象。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取。

如果没有获取到,也就是缓存中没有这个bean对象,那就先实例化A,然后再放入三级缓存中。

调用AbstractAutowireCapableBeanFactory类中的doCreateBean方法,实例化A。
spring如何解决循环依赖问题_第10张图片
实例化完成后,调用DefaultSingletonBeanRegistry类中的addSingletonFactory方法,将bean 放入到三级缓存中。
spring如何解决循环依赖问题_第11张图片
这里就是解决循环依赖的关键,这段代码发生在doCreateBean之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用(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三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

而如果在三级缓存中获取到了bean对象, 则将bean对象从三级缓存singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。
spring如何解决循环依赖问题_第12张图片

四、为什么不用二级缓存?

理论上二级缓存时可行的,只需要将三级缓存中BeanFactory创建的对象提前放入二级缓存中,这样三级缓存就可以移除了。但是,

如果要使用二级缓存解决循环依赖,意味着Bean在构造完后就创建代理对象,这样违背了Spring设计原则。Spring结合AOP跟Bean的生命周期,是在Bean创建完全之后通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

另一种说法是:初始spring是没有解决循环引用问题的,设计原则是 bean 实例化、属性设置、初始化之后 再 生成aop对象,但是为了解决循环依赖但又尽量不打破这个设计原则的情况下,使用了存储了函数式接口的第三级缓存; 如果使用二级缓存的话,可以将aop的代理工作提前到 提前暴露实例的阶段执行; 也就是说所有的bean在创建过程中就先生成代理对象再初始化和其他工作; 但是这样的话,就和spring的aop的设计原则相驳,aop的实现需要与bean的正常生命周期的创建分离; 这样只有使用第三级缓存封装一个函数式接口对象到缓存中, 发生循环依赖时,触发代理类的生成;
spring如何解决循环依赖问题_第13张图片

五、图解三级缓存流程

spring如何解决循环依赖问题_第14张图片
spring如何解决循环依赖问题_第15张图片
spring如何解决循环依赖问题_第16张图片
spring如何解决循环依赖问题_第17张图片

你可能感兴趣的:(spring,spring,java)