关于spring:Spring源码分析之循环依赖及解决方案

40次阅读

共计 12688 个字符,预计需要花费 32 分钟才能阅读完成。

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

@Component
public 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

@Component
public 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;}
}

工厂对象

@Component
public 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;}
}
@Component
public 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 源码剖析之申请链过程

另外笔者公众号:奇客工夫,有更多精彩的文章,有趣味的同学,能够关注

正文完
 0