Spring如何解決迴圈引用

陳其苗發表於2019-06-28

概念

什麼是迴圈引用?

故名思義,多個物件形成環路。

有哪幾種迴圈引用?

在Spring中存在如下幾種迴圈引用,一一舉例分析一下

  • 注入迴圈引用(Set注入 註解注入
package c.q.m;

import lombok.Data;

/**
 * @Auther: chenqimiao
 * @Date: 2019/6/28 11:16
 * @Description:
 */
@Data
public class You {
    private Me me;
}
package c.q.m;

import lombok.Data;

/**
 * @Auther: chenqimiao
 * @Date: 2019/6/28 11:16
 * @Description:
 */
@Data
public class Me {
    private You you;
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    
    <bean id="me" class="c.q.m.Me">
        <property name="you" ref="you"/>
    </bean>
    
    <bean id="you" class="c.q.m.You"> 
        <property name="me" ref="me"/>
    </bean>
    
</beans>
package c.q.m;

import org.junit.Assert;
import org.junit.Test;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * @Auther: chenqimiao
 * @Date: 2019/6/28 13:44
 * @Description:
 */
public class CircularReferenceTest {

    @Test
    public void injectionTest() {
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("circular-reference-beans-test-config.xml"));
        Me me = beanFactory.getBean(Me.class);
        You you = beanFactory.getBean(You.class);
        Assert.assertEquals(me.getYou(), you);
    }
}
  • 構造器迴圈引用
package c.q.m;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @Auther: chenqimiao
 * @Date: 2019/6/28 11:16
 * @Description:
 */
@AllArgsConstructor
@Getter
public class You {
    private Me me;
}
package c.q.m;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @Auther: chenqimiao
 * @Date: 2019/6/28 11:16
 * @Description:
 */
@AllArgsConstructor
@Getter
public class Me {
    private You you;
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="me" class="c.q.m.Me">
        <constructor-arg ref="you"/>
    </bean>

    <bean id="you" class="c.q.m.You">
        <constructor-arg ref="me"/>
    </bean>

</beans>
package c.q.m;

import org.junit.Assert;
import org.junit.Test;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * @Auther: chenqimiao
 * @Date: 2019/6/28 13:44
 * @Description:
 */
public class CircularReferenceTest {

    @Test
    public void constructorTest() {
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("circular-           reference-beans-test-config.xml"));
        Me me = beanFactory.getBean(Me.class);
        You you = beanFactory.getBean(You.class);
        Assert.assertEquals(me.getYou(), you);
    }
}
  • 工廠構造迴圈引用(與構造器迴圈引用類似)

Spring如何解決

提前暴露一個ObjectFactory 型別的工廠物件,通過這種方式Spring解決了單例模式下的注入迴圈引用,至於其他型別的迴圈引用Spring也並沒有什麼好的解決辦法。

通過原始碼的方式來分析一下Spring的手段

  • org.springframework.beans.factory.support.AbstractBeanFactory

    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
              @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    
          final String beanName = transformedBeanName(name);
          Object bean;
    
          // Eagerly check singleton cache for manually registered singletons.
          Object sharedInstance = getSingleton(beanName);
          if (sharedInstance != null && args == null) {
              if (logger.isTraceEnabled()) {
                  if (isSingletonCurrentlyInCreation(beanName)) {
                      logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
                              "' that is not fully initialized yet - a consequence of a circular reference");
                  }
                  else {
                      logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
                  }
              }
              bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
          }
    
          else {
              // Fail if we're already creating this bean instance:
              // We're assumably within a circular reference.
              if (isPrototypeCurrentlyInCreation(beanName)) {
                  throw new BeanCurrentlyInCreationException(beanName);
              }
    
              // Check if bean definition exists in this factory.
              BeanFactory parentBeanFactory = getParentBeanFactory();
              if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                  // Not found -> check parent.
                  String nameToLookup = originalBeanName(name);
                  if (parentBeanFactory instanceof AbstractBeanFactory) {
                      return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
                              nameToLookup, requiredType, args, typeCheckOnly);
                  }
                  else if (args != null) {
                      // Delegation to parent with explicit args.
                      return (T) parentBeanFactory.getBean(nameToLookup, args);
                  }
                  else if (requiredType != null) {
                      // No args -> delegate to standard getBean method.
                      return parentBeanFactory.getBean(nameToLookup, requiredType);
                  }
                  else {
                      return (T) parentBeanFactory.getBean(nameToLookup);
                  }
              }
    
              if (!typeCheckOnly) {
                  markBeanAsCreated(beanName);
              }
    
              try {
                  final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                  checkMergedBeanDefinition(mbd, beanName, args);
    
                  // Guarantee initialization of beans that the current bean depends on.
                  String[] dependsOn = mbd.getDependsOn();
                  if (dependsOn != null) {
                      for (String dep : dependsOn) {
                          if (isDependent(beanName, dep)) {
                              throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                      "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                          }
                          registerDependentBean(dep, beanName);
                          try {
                              getBean(dep);
                          }
                          catch (NoSuchBeanDefinitionException ex) {
                              throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                      "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                          }
                      }
                  }
    
                  // Create bean instance.
                  if (mbd.isSingleton()) {
                      sharedInstance = getSingleton(beanName, () -> {
                          try {
                              return createBean(beanName, mbd, args);
                          }
                          catch (BeansException ex) {
                              // Explicitly remove instance from singleton cache: It might have been put there
                              // eagerly by the creation process, to allow for circular reference resolution.
                              // Also remove any beans that received a temporary reference to the bean.
                              destroySingleton(beanName);
                              throw ex;
                          }
                      });
                      bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                  }
    
                  else if (mbd.isPrototype()) {
                      // It's a prototype -> create a new instance.
                      Object prototypeInstance = null;
                      try {
                          beforePrototypeCreation(beanName);
                          prototypeInstance = createBean(beanName, mbd, args);
                      }
                      finally {
                          afterPrototypeCreation(beanName);
                      }
                      bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
                  }
    
                  else {
                      String scopeName = mbd.getScope();
                      final Scope scope = this.scopes.get(scopeName);
                      if (scope == null) {
                          throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                      }
                      try {
                          Object scopedInstance = scope.get(beanName, () -> {
                              beforePrototypeCreation(beanName);
                              try {
                                  return createBean(beanName, mbd, args);
                              }
                              finally {
                                  afterPrototypeCreation(beanName);
                              }
                          });
                          bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                      }
                      catch (IllegalStateException ex) {
                          throw new BeanCreationException(beanName,
                                  "Scope '" + scopeName + "' is not active for the current thread; consider " +
                                  "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                                  ex);
                      }
                  }
              }
              catch (BeansException ex) {
                  cleanupAfterBeanCreationFailure(beanName);
                  throw ex;
              }
          }
    
          // Check if required type matches the type of the actual bean instance.
          if (requiredType != null && !requiredType.isInstance(bean)) {
              try {
                  T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
                  if (convertedBean == null) {
                      throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
                  }
                  return convertedBean;
              }
              catch (TypeMismatchException ex) {
                  if (logger.isTraceEnabled()) {
                      logger.trace("Failed to convert bean '" + name + "' to required type '" +
                              ClassUtils.getQualifiedName(requiredType) + "'", ex);
                  }
                  throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
              }
          }
          return (T) bean;
      }
    

    跟蹤上面的Object sharedInstance = getSingleton(beanName);方法

  • org.springframework.beans.factory.support.DefaultSingletonBeanRegistry

      @Override
      @Nullable
      public Object getSingleton(String beanName) {
          return getSingleton(beanName, true);
      }
    
      @Nullable
      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) {
              //*******singleFactory中拿到提前暴露的ObjectFactory物件******//
                      ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                      if (singletonFactory != null) {
                          singletonObject = singletonFactory.getObject();
                          this.earlySingletonObjects.put(beanName, singletonObject);
                          this.singletonFactories.remove(beanName);
                      }
                  }
              }
          }
          return singletonObject;
      }

    定位到這裡,發現Spring再獲取bean的時候,會去singletonFactories集合裡面拿一個ObjectFactory

    物件,再呼叫其getObject()方法拿到我們想要的bean.那麼問題來了,singletonFactories的物件是何時put進去的?帶著這個問題,我們跟蹤一下singletonFactories 這個集合。

    我們回到org.springframework.beans.factory.support.AbstractBeanFactorydoGetBean方法,定位到下面的程式碼段:

                  // Create bean instance.
                  if (mbd.isSingleton()) {
                      sharedInstance = getSingleton(beanName, () -> {
                          try {
                              return createBean(beanName, mbd, args);
                          }
                          catch (BeansException ex) {
                              // Explicitly remove instance from singleton cache: It might have been put there
                              // eagerly by the creation process, to allow for circular reference resolution.
                              // Also remove any beans that received a temporary reference to the bean.
                              destroySingleton(beanName);
                              throw ex;
                          }
                      });
                      bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                  }

    定義的bean是單例,則呼叫getSingleton方法,跟蹤這個方法發現它是屬於org.springframework.beans.factory.suppor.DefaultSingletonBeanRegistry 的

    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
          Assert.notNull(beanName, "Bean name must not be null");
          synchronized (this.singletonObjects) {
              Object singletonObject = this.singletonObjects.get(beanName);
              if (singletonObject == null) {
                  if (this.singletonsCurrentlyInDestruction) {
                      throw new BeanCreationNotAllowedException(beanName,
                              "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                              "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
                  }
                  if (logger.isDebugEnabled()) {
                      logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
                  }
                  beforeSingletonCreation(beanName);
                  boolean newSingleton = false;
                  boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
                  if (recordSuppressedExceptions) {
                      this.suppressedExceptions = new LinkedHashSet<>();
                  }
                  try {
                      singletonObject = singletonFactory.getObject();
                      newSingleton = true;
                  }
                  catch (IllegalStateException ex) {
                      // Has the singleton object implicitly appeared in the meantime ->
                      // if yes, proceed with it since the exception indicates that state.
                      singletonObject = this.singletonObjects.get(beanName);
                      if (singletonObject == null) {
                          throw ex;
                      }
                  }
                  catch (BeanCreationException ex) {
                      if (recordSuppressedExceptions) {
                          for (Exception suppressedException : this.suppressedExceptions) {
                              ex.addRelatedCause(suppressedException);
                          }
                      }
                      throw ex;
                  }
                  finally {
                      if (recordSuppressedExceptions) {
                          this.suppressedExceptions = null;
                      }
                      afterSingletonCreation(beanName);
                  }
                  if (newSingleton) {
                      addSingleton(beanName, singletonObject);
                  }
              }
              return singletonObject;
          }
      }

    這個方法提供了很多的擴充套件方法,但是我們關注的核心是這一句話

    singletonObject = singletonFactory.getObject();singletonFactory就是我們剛剛傳入的函式。

                          try {
                              return createBean(beanName, mbd, args);
                          }
                          catch (BeansException ex) {
                              // Explicitly remove instance from singleton cache: It might have been put there
                              // eagerly by the creation process, to allow for circular reference resolution.
                              // Also remove any beans that received a temporary reference to the bean.
                              destroySingleton(beanName);
                              throw ex;
                          }

    接著定位到createBean方法,它是屬於org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory的

      @Override
      protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
              throws BeanCreationException {
    
          if (logger.isTraceEnabled()) {
              logger.trace("Creating instance of bean '" + beanName + "'");
          }
          RootBeanDefinition mbdToUse = mbd;
    
          // Make sure bean class is actually resolved at this point, and
          // clone the bean definition in case of a dynamically resolved Class
          // which cannot be stored in the shared merged bean definition.
          Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
          if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
              mbdToUse = new RootBeanDefinition(mbd);
              mbdToUse.setBeanClass(resolvedClass);
          }
    
          // Prepare method overrides.
          try {
              mbdToUse.prepareMethodOverrides();
          }
          catch (BeanDefinitionValidationException ex) {
              throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
                      beanName, "Validation of method overrides failed", ex);
          }
    
          try {
              // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
              Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
              if (bean != null) {
                  return bean;
              }
          }
          catch (Throwable ex) {
              throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
                      "BeanPostProcessor before instantiation of bean failed", ex);
          }
    
          try {
              Object beanInstance = doCreateBean(beanName, mbdToUse, args);
              if (logger.isTraceEnabled()) {
                  logger.trace("Finished creating instance of bean '" + beanName + "'");
              }
              return beanInstance;
          }
          catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
              // A previously detected exception with proper bean creation context already,
              // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
              throw ex;
          }
          catch (Throwable ex) {
              throw new BeanCreationException(
                      mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
          }
      }

    跟蹤這一句話Object beanInstance = doCreateBean(beanName, mbdToUse, args);

      protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
              throws BeanCreationException {
    
          // Instantiate the bean.
          BeanWrapper instanceWrapper = null;
          if (mbd.isSingleton()) {
              instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
          }
          if (instanceWrapper == null) {
              instanceWrapper = createBeanInstance(beanName, mbd, args);
          }
          final Object bean = instanceWrapper.getWrappedInstance();
          Class<?> beanType = instanceWrapper.getWrappedClass();
          if (beanType != NullBean.class) {
              mbd.resolvedTargetType = beanType;
          }
    
          // Allow post-processors to modify the merged bean definition.
          synchronized (mbd.postProcessingLock) {
              if (!mbd.postProcessed) {
                  try {
                      applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
                  }
                  catch (Throwable ex) {
                      throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                              "Post-processing of merged bean definition failed", ex);
                  }
                  mbd.postProcessed = true;
              }
          }
    
          // Eagerly cache singletons to be able to resolve circular references
          // even when triggered by lifecycle interfaces like BeanFactoryAware.
          boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                  isSingletonCurrentlyInCreation(beanName));
          if (earlySingletonExposure) {
              if (logger.isTraceEnabled()) {
                  logger.trace("Eagerly caching bean '" + beanName +
                          "' to allow for resolving potential circular references");
              }
              addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
          }
    
          // Initialize the bean instance.
          Object exposedObject = bean;
          try {
              populateBean(beanName, mbd, instanceWrapper);
              exposedObject = initializeBean(beanName, exposedObject, mbd);
          }
          catch (Throwable ex) {
              if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
                  throw (BeanCreationException) ex;
              }
              else {
                  throw new BeanCreationException(
                          mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
              }
          }
    
          if (earlySingletonExposure) {
              Object earlySingletonReference = getSingleton(beanName, false);
              if (earlySingletonReference != null) {
                  if (exposedObject == bean) {
                      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.");
                      }
                  }
              }
          }
    
          // Register bean as disposable.
          try {
              registerDisposableBeanIfNecessary(beanName, bean, mbd);
          }
          catch (BeanDefinitionValidationException ex) {
              throw new BeanCreationException(
                      mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
          }
    
          return exposedObject;
      }
    

    真相差不多浮出水面了,請看這一句話

    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

      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);
              }
          }
      }

    this.singletonFactories.put(beanName, singletonFactory);就在這裡beanFactory被put進了singletonFactories.看一下singletonFactory具體的實現

      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;
      }
    

    將傳入的bean經過BeanPostProcessor的處理後返回。那麼傳入的bean到底是什麼狀態的bean呢?

    此時的bean是完成了初始化構造的bean,但是還沒有進行set或者註解注入的bean,是bean的一箇中間狀態。

    說到這裡應該差不多清晰了,Spring會在bean還未完全例項化的時候,將bean包裝在ObjectFactory裡面,呼叫doGetBean的時候,先嚐試去ObjectFactory中去拿還未完全例項化的bean.

    You and Me 的例子在註解注入造成迴圈依賴時,Spring的呼叫鏈時序圖如下:
    Spring如何解決迴圈引用

    總結

    Spring如何解決迴圈引用

相關文章