天天看點

Spring的循環依賴和三級緩存

前提

(1)作者技術比較差,文章寫的比較随意,也可能有錯誤,歡迎您指出。

(2)如果您不了解Spring Bean的聲明周期,那麼您可以看一下文章(​​Bean的生命周期_CBeann的部落格-​​)或者百度其它文章,然後在回來看該文章,否則個人感覺應該看不懂

解決循環依賴

假設有一種下面的情況,A中有B,B中有A

@Data
public class A {
  private B b;
  public A() {System.out.println("A 無參構造器。。。");}
  public void speak() {System.out.println("------AAA---------");}
}

@Data
public class B {
  public B() {System.out.println("B 無參構造器。。。");}
  private A a;
  public void speak() {System.out.println("------BBB---------");}
}      

圖檔分析

Spring的循環依賴和三級緩存

代碼分析

在建立的A的時候調用doCreateBean方法

1)調用A無參構造方法建立Bean

2)把該bean對象添加到三級緩存中(在下面的代碼中有注釋)

3)Bean的屬性指派,A的裡面引用了B,是以此時會調用doCteateBean(B)

//AbstractAutowireCapableBeanFactory
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {
      {

    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
      //省略
    }
    if (instanceWrapper == null) {
        //1)調用無參構造方法建立Bean
      instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    final Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
      mbd.resolvedTargetType = beanType;
    }

    
    synchronized (mbd.postProcessingLock) {
      //省略
    }

    
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
      if (logger.isTraceEnabled()) {
        //省略
      }
      //2)把該bean對象添加到三級緩存中,注意getEarlyBeanReference方法,特别有用
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        //3)Bean的屬性指派
      populateBean(beanName, mbd, instanceWrapper);
      //4)處理aware接口、applyBeanPostProcessorsBeforeInitialization、initMethod
      exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
      if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
        //省略
      }
      else {
        //省略
      }
    }

    if (earlySingletonExposure) {
      //省略
    }

    // Register bean as disposable.
    try {
      registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
      //省略
    }

    return exposedObject;
  }





}      

 此時在建立B的時候調用getBean(A),然後會走到下面代碼的地方,從三級緩存中擷取到A(B=null),傳回該不完整的A的位址,然後B建立成功,然後繼續建立A,然後A也建立成功。

-------------------------源碼1

//DefaultSingletonBeanRegistry
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //從一級緩存中擷取,即IOC容器,即完整的Bean對象
    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;
  }      

循環依賴總結

       (1)建立A的時候調用A的無參構造方法,然後在把得到的位址A(B=null)放入到三級緩存中,然後填充自己的屬性B,也就會建立B;

       (2)當建立B的時候,填充自己的屬性A,從三級緩存中拿到A(B=null)位址,然後B建立成功;

       (3)此時回到(1),此時拿到B,然後完善A,建立A成功。

       (4)因為在(2)中拿到的是A的位址,是以在(3)中完善A在B中是一個。      

三級緩存

疑問

個人感覺二級緩存足矣,為什麼還要三級緩存?

反駁疑問

假設下面的場景:隻有singletonObject(第一級緩存)和singletonFactory (第三級緩存),即沒有earlySingletonObjects(第二級緩存)

如果有這麼一種情況A(B),B(A),還有一個AOP是關注A的某個方法

此時的邏輯為:

1)建立A

2)把A(B=null)的位址(abc)存入singletonFactory緩存中

3)建立B

4)B在指派a屬性的時候,在singletonFactory緩存中拿出A的位址(abc)并且指派給屬性a(左邊這句話是錯的)(這就是三級緩存的關鍵),

     4.1)沒有AOP的時候,确實是存的a的位址,沒錯,傳回的也是a的位址。

      4.2)如果有AOP,确實存進去的是a的位址,但是傳回的已經不是A的位址了,是A的代理對象位址(看源碼2,3,4)。

總結:此時就出現問題了,如果沒有earlySingletonObjects(第二級緩存),那麼每次在singletonFactory (第三級緩存)中拿到的A對象都會建立建立一個代理對象,即每次向依賴A的對象中賦的值都是不同的代理對象,那麼就不符合單例模式了。

-------------------------源碼2

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
          SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
          exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
        }
      }
    }
    return exposedObject;
  }      

-------------------------源碼3 

//AbstractAutoProxyCreator
@Override
  public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
        //跟進去
    return wrapIfNecessary(bean, beanName, cacheKey);
  }      

-------------------------源碼4

//AbstractAutoProxyCreator
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
      return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
    }

    // Create proxy if we have advice.
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
      this.advisedBeans.put(cacheKey, Boolean.TRUE);
            //傳回了一個新對象,新位址
            //傳回了一個新對象,新位址
            //傳回了一個新對象,新位址
      Object proxy = createProxy(
          bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
      this.proxyTypes.put(cacheKey, proxy.getClass());
      return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
  }      

總結

1)在沒有AOP的情況下二級緩存足矣解決循環依賴,三級緩存更能解決問題。

2)三級緩存其實也是解決循環依賴的,是解決帶AOP的循環依賴的,如上文中舉的例子。如果您查的三級緩存資料沒有說AOP,個人感覺這篇文章寫的不是很充實。

本文沒有回答的疑問

疑問1

上問中反駁二級緩存不能解決帶AOP的循環依賴問題時,是把earlySingletonObjects(第二級緩存)去掉;如果我說我去掉singletonFactory (第三級緩存),那該如何反駁二級緩存不能解決帶AOP的循環依賴問題呢???

疑問2

就拿上問中舉的例字來說,A依賴B,B依賴A,有一個關注A的AOP。

下面是建立Bean聲明周期的一段代碼,以建立A為例

//AbstractAutowireCapableBeanFactory
protected Object doCreateBean{
//建立A
Object exposedObject = bean;
    try {
            //初始化A,因為A中有屬性B,此時去建立B,然後把A的代理對象存入earlySingletonObjects緩存中,B建立完畢,然後又回到此處繼續初始化A
      populateBean(beanName, mbd, instanceWrapper);
            //為非代理對象A執行aware接口等等
      exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
      //省略
      }
    }

    if (earlySingletonExposure) {
            //在earlySingletonObjects中拿到代理對象A
      Object earlySingletonReference = getSingleton(beanName, false);
      if (earlySingletonReference != null) {
        if (exposedObject == bean) {
                    //把exposedObject由指向非代理對象A變為指向代理對象A,那麼
                    //exposedObject = initializeBean(beanName, exposedObject, mbd);
                    //我認為是白做了,我不清楚這個地方???????????????
          exposedObject = earlySingletonReference;
        }
        else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
          String[] dependentBeans = getDependentBeans(beanName);
          Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
          for (String dependentBean : dependentBeans) {
            if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
              actualDependentBeans.add(dependentBean);
            }
          }
          if (!actualDependentBeans.isEmpty()) {
            throw new BeanCurrentlyInCreationException(beanName,
                "Bean with name '" + beanName + "' has been injected into other beans [" +
                StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                "] in its raw version as part of a circular reference, but has eventually been " +
                "wrapped. This means that said other beans do not use the final version of the " +
                "bean. This is often the result of over-eager type matching - consider using " +
                "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
          }
        }
      }

}      

如果有知道上面兩個問題答案的,可以在下問中評論,一起學習,共同進步

參考

看為什麼要三級緩存的話從70分鐘開始看