Spring源码剖析之循环依赖及解决方案

注释:

首先,咱们须要明确什么是循环依赖?简略来说就是A对象创立过程中须要依赖B对象,而B对象创立过程中同样也须要A对象,所以A创立时须要先去把B创立进去,但B创立时又要先把A创立进去...死循环有木有...

那么在Spring中,有多少种循环依赖的状况呢?大部分人只晓得两个一般的Bean之间的循环依赖,而Spring中其实存在三种对象(一般Bean,工厂Bean,代理对象),他们之间都会存在循环依赖,这里我给列举进去,大抵别离以下几种:

  • 一般Bean与一般Bean之间
  • 一般Bean与代理对象之间
  • 代理对象与代理对象之间
  • 一般Bean与工厂Bean之间
  • 工厂Bean与工厂Bean之间
  • 工厂Bean与代理对象之间

那么,在Spring中是如何解决这个问题的呢?

1. 一般Bean与一般Bean

首先,咱们先构想一下,如果让咱们本人来编码,咱们会如何解决这个问题?

栗子

当初咱们有两个相互依赖的对象A和B

public class NormalBeanA {    private NormalBeanB normalBeanB;    public void setNormalBeanB(NormalBeanB normalBeanB) {        this.normalBeanB = normalBeanB;    }}
public class NormalBeanB {    private NormalBeanA normalBeanA;    public void setNormalBeanA(NormalBeanA normalBeanA) {        this.normalBeanA = normalBeanA;    }}

而后咱们想要让他们彼此都含有对象

public class Main {    public static void main(String[] args) {        // 先创立A对象        NormalBeanA normalBeanA = new NormalBeanA();        // 创立B对象        NormalBeanB normalBeanB = new NormalBeanB();        // 将A对象的援用赋给B        normalBeanB.setNormalBeanA(normalBeanA);        // 再将B赋给A        normalBeanA.setNormalBeanB(normalBeanB);    }}

发现了吗?咱们并没有先创立一个残缺的A对象,而是先创立了一个空壳对象(Spring中称为晚期对象),将这个晚期对象A先赋给了B,使得失去了一个残缺的B对象,再将这个残缺的B对象赋给A,从而解决了这个循环依赖问题,so easy!

那么Spring中是不是也这样做的呢?咱们就来看看吧~

Spring中的解决方案

因为上一篇曾经剖析过Bean的创立过程了,其中的某些局部就不再细讲了
先来到创立Bean的办法
AbstractAutowireCapableBeanFactory#doCreateBean

假如此时在创立A

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){  // beanName -> A  // 实例化A  BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);  // 是否容许裸露晚期对象  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&                                    isSingletonCurrentlyInCreation(beanName));  if (earlySingletonExposure) {    // 将获取晚期对象的回调办法放到三级缓存中    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));  }}

addSingletonFactory

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {        synchronized (this.singletonObjects) {            // 单例缓存池中没有该Bean            if (!this.singletonObjects.containsKey(beanName)) {                // 将回调函数放入三级缓存                this.singletonFactories.put(beanName, singletonFactory);                this.earlySingletonObjects.remove(beanName);                this.registeredSingletons.add(beanName);            }        }    }
ObjectFactory是一个函数式接口

在这里,咱们发现在创立Bean时,Spring不管三七二十一,间接将一个获取晚期对象的回调办法放进了一个三级缓存中,咱们再来看一下回调办法的逻辑

getEarlyBeanReference

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {  Object exposedObject = bean;  // 调用BeanPostProcessor对晚期对象进行解决,在Spring的内置处理器中,并无相干的解决逻辑  // 如果开启了AOP,将引入一个AnnotationAwareAspectJAutoProxyCreator,此时将可能对Bean进行动静代理  if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {    for (BeanPostProcessor bp : getBeanPostProcessors()) {      if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {        SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;        exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);      }    }  }  return exposedObject;}
在这里,如果没有开启AOP,或者该对象不须要动静代理,会间接返回原对象

此时,曾经将A的晚期对象缓存起来了,接下来在填充属性时会产生什么呢?

置信大家也应该想到了,A对象填充属性时必然发现依赖了B对象,此时就将转头创立B,在创立B时同样会经验以上步骤,此时就该B对象填充属性了,这时,又将要转头创立A,那么,当初会有什么不一样的中央呢?咱们看看getBean的逻辑吧

doGetBean

protected <T> T doGetBean(            String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){  // 此时beanName为A  String beanName = transformedBeanName(name);  // 尝试从三级缓存中获取bean,这里很要害  Object sharedInstance = getSingleton(beanName);}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {  // 从单例缓存池中获取,此时依然是取不到的  Object singletonObject = this.singletonObjects.get(beanName);  // 获取不到,判断bean是否正在创立,没错,此时A的确正在创立  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {    // 因为当初依然是在同一个线程,基于同步锁的可重入性,此时不会阻塞    synchronized (this.singletonObjects) {      // 从晚期对象缓存池中获取,这里是没有的      singletonObject = this.earlySingletonObjects.get(beanName);      if (singletonObject == null && allowEarlyReference) {        // 从三级缓存中获取回调函数,此时就获取到了咱们在创立A时放入的回调函数        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);        if (singletonFactory != null) {          // 调用回调办法获取晚期bean,因为咱们当初探讨的是一般对象,所以返回原对象          singletonObject = singletonFactory.getObject();          // 将晚期对象放到二级缓存,移除三级缓存          this.earlySingletonObjects.put(beanName, singletonObject);          this.singletonFactories.remove(beanName);        }      }    }  }  // 返回晚期对象A  return singletonObject;}

震惊!此时咱们就拿到了A的晚期对象进行返回,所以B得以被填充属性,B创立结束后,又将返回到A填充属性的过程,A也得以被填充属性,A也创立结束,这时,A和B都创立好了,循环依赖问题得以开场~

一般Bean和一般Bean之间的问题就到这里了,不晓得小伙伴们有没有晕呢~

2. 一般Bean和代理对象

一般Bean和代理对象之间的循环依赖与两个一般Bean的循环依赖其实大致相同,只不过是多了一次动静代理的过程,咱们假如A对象是须要代理的对象,B对象依然是一个一般对象,而后,咱们开始创立A对象。

刚开始创立A的过程与下面的例子是截然不同的,紧接着天然是须要创立B,而后B依赖了A,于是又倒回去创立A,此时,再次走到去缓存池获取的过程。

// 从三级缓存中获取回调函数ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {  // 调用回调办法获取晚期bean,此时返回的是一个A的代理对象  singletonObject = singletonFactory.getObject();  // 将晚期对象放到二级缓存,移除三级缓存  this.earlySingletonObjects.put(beanName, singletonObject);  this.singletonFactories.remove(beanName);}

这时就不太一样了,在singletonFactory.getObject()时,因为此时A是须要代理的对象,在调用回调函数时,就会触发动静代理的过程

AbstractAutoProxyCreator#getEarlyBeanReference

public Object getEarlyBeanReference(Object bean, String beanName) {  // 生成一个缓存Key  Object cacheKey = getCacheKey(bean.getClass(), beanName);  // 放入缓存中,用于在初始化后调用该后置处理器时判断是否进行动静代理过  this.earlyProxyReferences.put(cacheKey, bean);  // 将对象进行动静代理  return wrapIfNecessary(bean, beanName, cacheKey);}

此时,B在创立时填充的属性就是A的代理对象了,B创立结束,返回到A的创立过程,但此时的A依然是一个一般对象,可B援用的A曾经是个代理对象了,不晓得小伙伴看到这里有没有蛊惑呢?

不急,让咱们持续往下走,填充完属性天然是须要初始化的,在初始化后,会调用一次后置处理器,咱们看看会不会有答案吧

初始化

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {    //...省略后面的步骤...  // 调用初始化办法  invokeInitMethods(beanName, wrappedBean, mbd);  // 解决初始化后的bean  wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);}

在解决初始化后的bean,又会调用动静代理的后置处理器了

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {  if (bean != null) {    Object cacheKey = getCacheKey(bean.getClass(), beanName);    // 判断缓存中是否有该对象,有则阐明该对象已被动静代理,跳过    if (this.earlyProxyReferences.remove(cacheKey) != bean) {      return wrapIfNecessary(bean, beanName, cacheKey);    }  }  return bean;}

不晓得小伙伴发现没有,earlyProxyReferences这个缓存可不就是咱们在填充B的属性,进而从缓存中获取A时放进去的吗?不信您往上翻到getEarlyBeanReference的步骤看看~

所以,此时并未进行任何解决,仍旧返回了咱们的原对象A,看来这里并没有咱们要的答案,那就持续吧~

// 是否容许裸露晚期对象if (earlySingletonExposure) {  // 从缓存池中获取晚期对象  Object earlySingletonReference = getSingleton(beanName, false);  if (earlySingletonReference != null) {    // bean为初始化前的对象,exposedObject为初始化后的对象    // 判断两对象是否相等,基于下面的剖析,这两者是相等的    if (exposedObject == bean) {      // 将晚期对象赋给exposedObject      exposedObject = earlySingletonReference;    }  }}

咱们来剖析一下下面的逻辑,getSingleton从缓存池中获取晚期对象返回的是什么呢?

synchronized (this.singletonObjects) {  // 从晚期对象缓存池中获取,此时就拿到了咱们填充B属性时放入的A的代理对象  singletonObject = this.earlySingletonObjects.get(beanName);  if (singletonObject == null && allowEarlyReference) {    // 从三级缓存中获取回调函数    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);    if (singletonFactory != null) {      // 调用回调办法获取晚期bean      singletonObject = singletonFactory.getObject();      // 将晚期对象放到二级缓存,移除三级缓存      this.earlySingletonObjects.put(beanName, singletonObject);      this.singletonFactories.remove(beanName);    }  }}

发现了吗?此时咱们就获取到了A的代理对象,而后咱们又把这个对象赋给了exposedObject,此时创建对象的流程走完,咱们失去的A不就是个代理对象了吗~

此次栗子是先创立须要代理的对象A,假如咱们先创立一般对象B会产生什么呢?

3. 代理对象与代理对象

代理对象与代理对象的循环依赖是怎么样的呢?解决过程又是如何呢?这里就留给小伙伴本人思考了,其实和一般Bean与代理对象是截然不同的,小伙伴想想是不是呢,这里我就不做剖析了。

4. 一般Bean与工厂Bean

这里所说的一般Bean与工厂Bean并非指bean与FactoryBean,这将毫无意义,而是指一般Bean与FactoryBean的getObject办法产生了循环依赖,因为FactoryBean最终产生的对象是由getObject办法所产出。咱们先来看看栗子吧~

假如工厂对象A依赖一般对象B,一般对象B依赖一般对象A。

小伙伴看到这里就可能问了,诶~你这不对呀,怎么成了「一般对象B依赖一般对象A」呢?不应该是工厂对象A吗?是这样的,在Spring中,因为一般对象A是由工厂对象A产生,所有在一般对象B想要获取一般对象A时,其实最终寻找调用的是工厂对象A的getObject办法,所以只有一般对象B依赖一般对象A就能够了,Spring会主动帮咱们把一般对象B和工厂对象A分割在一起。

小伙伴,哦~

一般对象A

public class NormalBeanA {    private NormalBeanB normalBeanB;    public void setNormalBeanB(NormalBeanB normalBeanB) {        this.normalBeanB = normalBeanB;    }}

工厂对象A

@Componentpublic class FactoryBeanA implements FactoryBean<NormalBeanA> {    @Autowired    private ApplicationContext context;    @Override    public NormalBeanA getObject() throws Exception {        NormalBeanA normalBeanA = new NormalBeanA();        NormalBeanB normalBeanB = context.getBean("normalBeanB", NormalBeanB.class);        normalBeanA.setNormalBeanB(normalBeanB);        return normalBeanA;    }    @Override    public Class<?> getObjectType() {        return NormalBeanA.class;    }}

一般对象B

@Componentpublic class NormalBeanB {    @Autowired    private NormalBeanA normalBeanA;}

假如咱们先创建对象A

因为FactoryBean和Bean的创立过程是一样的,只是多了步getObject,所以咱们间接定位到调用getObject入口

if (mbd.isSingleton()) {  // 开始创立bean  sharedInstance = getSingleton(beanName, () -> {    // 创立bean    return createBean(beanName, mbd, args);  });  // 解决FactoryBean  bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}
protected Object getObjectForBeanInstance(            Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {    // 先尝试从缓存中获取,保障屡次从工厂bean获取的bean是同一个bean  object = getCachedObjectForFactoryBean(beanName);  if (object == null) {    // 从FactoryBean获取对象    object = getObjectFromFactoryBean(factory, beanName, !synthetic);  }}
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {    // 加锁,避免多线程时反复创立bean  synchronized (getSingletonMutex()) {    // 这里是Double Check    Object object = this.factoryBeanObjectCache.get(beanName);    if (object == null) {      // 获取bean,调用factoryBean的getObject()      object = doGetObjectFromFactoryBean(factory, beanName);    }    // 又从缓存中取了一次,why? 咱们缓缓剖析    Object alreadyThere = this.factoryBeanObjectCache.get(beanName);    if (alreadyThere != null) {      object = alreadyThere;    }else{      // ...省略初始化bean的逻辑...      // 将获取到的bean放入缓存      this.factoryBeanObjectCache.put(beanName, object);    }  }}
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName){  return factory.getObject();}

当初,就走到了咱们自定义的getObject办法,因为咱们调用了context.getBean("normalBeanB", NormalBeanB.class),此时,将会去创立B对象,在创立过程中,先将B的晚期对象放入三级缓存,紧接着填充属性,发现依赖了A对象,又要倒回来创立A对象,从而又回到下面的逻辑,再次调用咱们自定义的getObject办法,这个时候会产生什么呢?

又要去创立B对象...(Spring:心好累)

然而!此时咱们在创立B时,是间接通过getBean在缓存中获取到了B的晚期对象,得以返回了!于是咱们自定义的getObject调用胜利,返回了一个残缺的A对象!

然而此时FactoryBean的缓冲中还是什么都没有的。

// 又从缓存中取了一次Object alreadyThere = this.factoryBeanObjectCache.get(beanName);if (alreadyThere != null) {  object = alreadyThere;}

这一次取alreadyThere必然是null,流程继续执行,将此时将获取到的bean放入缓存

this.factoryBeanObjectCache.put(beanName, object);

从FactoryBean获取对象的流程完结,返回到创立B的过程中,B对象此时的属性也得以填充,再返回到第一次创立A的过程,也就是咱们第一次调用自定义的getObject办法,调用结束,返回到这里

// 获取bean,调用factoryBean的getObject()object = doGetObjectFromFactoryBean(factory, beanName);Object alreadyThere = this.factoryBeanObjectCache.get(beanName);if (alreadyThere != null) {  object = alreadyThere;

那么,此时this.factoryBeanObjectCache.get(beanName)能从缓冲中拿到对象了吗?有没有发现,拿到了刚刚B对象填充属性时再次创立A对象放进去的!

所以,明确这里为什么要再次从缓存中获取了吧?就是为了解决因为循环依赖时调用了两次自定义的getObject办法,从而创立了两个不雷同的A对象,保障咱们返回进来的A对象惟一!

怕小伙伴晕了,画个图给大家

5. 工厂Bean与工厂Bean之间

咱们曾经举例4种循环依赖的栗子,Spring都有所解决,那么有没有Spring也无奈解决的循环依赖问题呢?

有的!就是这个FactoryBeanFactoryBean的循环依赖!

假如工厂对象A依赖工厂对象B,工厂对象B依赖工厂对象A,那么,这次的栗子会是什么样呢?

一般对象

public class NormalBeanA {    private NormalBeanB normalBeanB;    public void setNormalBeanB(NormalBeanB normalBeanB) {        this.normalBeanB = normalBeanB;    }}
public class NormalBeanB {    private NormalBeanA normalBeanA;    public void setNormalBeanA(NormalBeanA normalBeanA) {        this.normalBeanA = normalBeanA;    }}

工厂对象

@Componentpublic class FactoryBeanA implements FactoryBean<NormalBeanA> {    @Autowired    private ApplicationContext context;    @Override    public NormalBeanA getObject() throws Exception {        NormalBeanA normalBeanA = new NormalBeanA();        NormalBeanB normalBeanB = context.getBean("factoryBeanB", NormalBeanB.class);        normalBeanA.setNormalBeanB(normalBeanB);        return normalBeanA;    }    @Override    public Class<?> getObjectType() {        return NormalBeanA.class;    }}
@Componentpublic class FactoryBeanB implements FactoryBean<NormalBeanB> {    @Autowired    private ApplicationContext context;    @Override    public NormalBeanB getObject() throws Exception {        NormalBeanB normalBeanB = new NormalBeanB();        NormalBeanA normalBeanA = context.getBean("factoryBeanA", NormalBeanA.class);        normalBeanB.setNormalBeanA(normalBeanA);        return normalBeanB;    }    @Override    public Class<?> getObjectType() {        return NormalBeanB.class;    }}

首先,咱们开始创建对象A,此时为调用工厂对象A的getObject办法,转而去获取对象B,便会走到工厂对象B的getObject办法,而后又去获取对象A,又将调用工厂对象A的getObject,再次去获取对象B,于是再次走到工厂对象B的getObject办法......此时,已经验了一轮循环,却没有跳出循环的迹象,妥妥的死循环了。

咱们画个图吧~

没错!这个图就是这么简略,因为始终无奈创立出一个对象,不论是晚期对象或者残缺对象,使得两个工厂对象重复的去获取对方,导致陷入了死循环。

那么,咱们是否有方法解决这个问题呢?

我的答案是无奈解决,如果有想法的小伙伴也能够本人想一想哦~

咱们发现,在产生循环依赖时,只有循环链中的某一个点能够先创立出一个晚期对象,那么在下一次循环时,就会使得咱们可能获取到晚期对象从而跳出循环!

而因为工厂对象与工厂对象间是无奈创立出这个晚期对象的,无奈满足跳出循环的条件,导致变成了死循环。

那么此时Spring中会抛出一个什么样的异样呢?

当然是栈溢出异样啦!两个工厂对象始终互相调用,一直开拓栈帧,可不就是栈溢出有木有~

6. 工厂对象与代理对象

下面的状况是无奈解决循环依赖的,那么这个状况能够解决吗?

答案是能够的!

咱们剖析了,一个循环链是否可能失去终止,关键在于是否可能在某个点创立出一个晚期对象(长期对象),而代理对象在doCreateBean时,是会生成一个晚期对象放入三级缓存的,于是该循环链得以终结。

具体过程我这里就不再细剖析了,就交由小伙伴本人入手吧~

总结

以上咱们一共举例了6种状况,通过剖析,总结出这样一条定律:

在产生循环依赖时,判断一个循环链是否可能失去终止,关键在于是否可能在某个点创立出一个晚期对象(长期对象),那么在下一次循环时,咱们就能通过该晚期对象进而跳出(突破)循环!

通过这样的定律,咱们得出工厂Bean与工厂Bean之间是无奈解决循环依赖的,那么还有其余状况无奈解决循环依赖吗?

有的!以上的例子举的都是单例的对象,并且都是通过set办法造成的循环依赖。

倘若咱们是因为构造方法造成的循环依赖呢?是否有解决办法吗?

没有,因为这并不满足咱们得出的定律

无奈执行结束构造方法,天然无奈创立出一个晚期对象。

倘若咱们的对象是多例的呢?

也不能,因为多例的对象在每次创立时都是创立新的对象,即便可能创立出晚期对象,也不能为下一次循环所用!

好了,本文就到这里完结了,心愿小伙伴们有所播种~

Spring IOC的外围局部到此篇就完结了,下一篇就让咱们进行AOP之旅吧~

下文预报:Spring源码剖析之AOP从解析到调用

Spring 源码系列
  1. Spring源码剖析之 IOC 容器预启动流程(已完结)
  2. Spring源码剖析之BeanFactory体系结构(已完结)
  3. Spring源码剖析之BeanFactoryPostProcessor调用过程(已完结)
  4. Spring源码剖析之Bean的创立过程(已完结)
  5. Spring源码剖析之什么是循环依赖及解决方案
  6. Spring源码剖析之AOP从解析到调用
  7. Spring源码剖析之事务管理(上),事物治理是spring作为容器的一个特点,总结一下他的根本实现与原理吧
  8. Spring源码剖析之事务管理(下) ,对于他的底层事物隔离与事物流传原理,重点剖析一下
Spring Mvc 源码系列
  1. SpringMvc体系结构
  2. SpringMvc源码剖析之Handler解析过程
  3. SpringMvc源码剖析之申请链过程
另外笔者公众号:奇客工夫,有更多精彩的文章,有趣味的同学,能够关注