spring5.x-solve circular dependency analysis

What is a circular dependency?

When an object is nested with multiple layers of objects, why doesn’t it cause an infinite loop? Or by how special treatment?

0e853e6dda930ffc98fc0a0308bbe173.png

Code Implementation

package com.hong.model;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.Serializable;

/**
 * @ClassName OrderInfo
 * @Description order bean
 * @Author csh
 * @Date 2023/2/17 17:19
 */
@Component
public class OrderInfo{
  //order number
  private String orderNo;
  //Payment status
  private String payStatus;
  //User Info
  private User userInfo;

  public String getOrderNo() {
    return orderNo;
  }

  public void setOrderNo(String orderNo) {
    this. orderNo = orderNo;
  }

  public String getPayStatus() {
    return payStatus;
  }

  public void setPayStatus(String payStatus) {
    this.payStatus = payStatus;
  }

  public User getUserInfo() {
    return userInfo;
  }

  public void setUserInfo(User userInfo) {
    this. userInfo = userInfo;
  }

  public OrderInfo() {
    System.out.println("The order is being initialized...");
    this.orderNo = "123456";
  }

  @Override
  public String toString() {
    return "OrderInfo{" +
        "orderNo='" + orderNo + ''' +
        ", payStatus='" + payStatus + ''' +
        ", userInfo=" + userInfo +
        '}';
  }
}
package com.hong.model;

import org.springframework.stereotype.Component;

import java.io.Serializable;

/**
 * @ClassName User
 * @Description user
 * @Author csh
 * @Date 2023/1/13 14:27
 */
@Component
public class User{
  private String userName;
  private int age;
  private String nickName;
  private OrderInfo orderInfo;


  public String getUserName() {
    return userName;
  }

  public void setUserName(String userName) {
    this. userName = userName;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this. age = age;
  }

  public String getNickName() {
    return nickName;
  }

  public void setNickName(String nickName) {
    this.nickName = nickName;
  }

  public User() {
    System.out.println("User is initializing...");
    this.userName = "hong";
  }


  public OrderInfo getOrderInfo() {
    return orderInfo;
  }

  public void setOrderInfo(OrderInfo orderInfo) {
    this. orderInfo = orderInfo;
  }

  @Override
  public String toString() {
    return "User{" +
        "userName='" + userName + ''' +
        ", age=" + age +
        ", nickName='" + nickName + ''' +
        ", orderInfo=" + orderInfo +
        '}';
  }
}
public static void main(String[] args) {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext((TestMain.class));
    OrderInfo orderInfo = applicationContext. getBean(OrderInfo. class);
    System.out.println(orderInfo.toString());
    User user = applicationContext. getBean(User. class);
    System.out.println(user.toString());
  }

What circular dependencies does spring only resolve?

Location

whether to solve

property setter injection

yes

property auto-injection

yes

For example: @Autowired

Constructor auto-injection

yes

Constructor circularly depends on another object

no

A constructor depends on another constructor

spring three-level cache

cache name

Description

Remarks

singletonObjects

The cache stores the Bean instances that have been created, that is, the Bean in the singleton mode. After the Bean is created, put the Bean instance into the cache.

level one

earlySingletonObjects

The cache stores the Bean instance being created, that is, the Bean that is in the instantiation and dependency injection phase when the Bean is being created. When the Bean is created, the Bean instance is put into the cache to solve the circular dependency problem.

Secondary

singletonFactories

The cache stores the Bean instance factory being created, that is, the factory object used to create the Bean instance. When the bean is created, if the bean needs to be proxied, the proxy factory object of the bean will be put into the cache.

Level three

The role of the third-level cache is to ensure thread safety and circular dependency resolution during the Bean creation process. Among them, the Bean instances in the singletonObjects cache are thread-safe, but the Bean instances in the earlySingletonObjects and singletonFactories caches are not thread-safe, and thread safety needs to be guaranteed through a synchronization mechanism. At the same time, earlySingletonObjects and singletonFactories caching is also the key to solving the circular dependency problem. When there is a circular dependency between two beans, the infinite loop problem caused by the circular dependency can be avoided by obtaining the Bean instance from the cache.

Source code learning

Initialize beans

When spring enters the finishBeanFactoryInitialization in refresh() during initialization, the remaining non-lazy-loaded beans will be instantiated, including the properties that the object depends on. The specific follow-up is as follows.

in org.springframework.context.support.AbstractApplicationContext#refresh

...
// Instantiate all remaining (non lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
...

Then enter the initialization of the Bean factory of the context, and initialize all remaining singleton beans in which preInstantiateSingletons will instantiate properties

// Instantiate all remaining (non lazy initialization) singletons
    beanFactory. preInstantiateSingletons();
@Override
  public void preInstantiateSingletons() throws BeansException {
    ...
        // get beans
        getBean(beanName);
        ...
  }

Called the doGetBean method to get the bean instance

@Override
  public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
  }

doGetBean calls getSingleton to get it from the cache, but the first time it comes in here is actually empty.

Then it will judge whether mbd.isSingleton() is a singleton, and if it calls getSingleton.. to create a bean.

/**
   * Returns an instance of the specified Bean, which may be shared or independent.
   * @param name the name of the bean to retrieve
   * @param requiredType type
   * @param args arguments to use when creating a bean instance using explicit arguments
   * (only applied when creating a new instance as opposed to retrieving an existing one)
   * @param typeCheckOnly whether the instance is obtained for a type check,
   * not for actual use
   * @return Bean instance
   * @throws BeansException if the bean could not be created
   */
  @SuppressWarnings("unchecked")
  protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
      @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    //Get the bean name
    final String beanName = transformedBeanName(name);
    Object bean;

    // Get an instance from the cache (core method ****)
    Object sharedInstance = getSingleton(beanName);
    
          ...
        // Determine whether it is a singleton mode
        if (mbd. isSingleton()) {
          // get instance
          sharedInstance = getSingleton(beanName, () -> {
            try {
              //Create bean and return
              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);
        }
        // determine whether it is a prototype
        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);
        }

        ...
    return (T) bean;
  }

doCreateBean mainly does several things here,

  1. Create beans: createBeanInstance

  2. Put it in the cache: addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

  3. Populate property: populateBean(beanName, mbd, instanceWrapper); solves the problem of property dependence here.

  4. Finally return: exposedObject

/**
   * Preprocessing creates the specified bean. Here is the implementation of the initialization properties in the third-level cache.
   * @param beanName the name of the bean
   * @param mbd the merged bean definition for the bean
   * @param args explicit arguments to use for constructor or factory method invocation
   * @return a new instance of the bean
   * @throws BeanCreationException if the bean could not be created
   * @see #instantiateBean
   * @see #instantiateUsingFactoryMethod
   * @see #autowireConstructor
   */
  protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {

    // instantiate the bean
    BeanWrapper instanceWrapper = null;
    //Single case judgment
    if (mbd. isSingleton()) {
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
      //Create a singleton bean and return it with BeanWrapper.
      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() & amp; & amp; this. allowCircularReferences & amp; & amp;
        isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
      if (logger. isTraceEnabled()) {
        logger.trace("Eagerly caching bean '" + beanName +
            "'to allow for resolving potential circular references");
      }
      //Add to the singletonFactories factory cache
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
      //Fill dependencies Here is the implementation of parsing circular dependencies
      populateBean(beanName, mbd, instanceWrapper);
      exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
      if (ex instanceof BeanCreationException & amp; & amp; 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 & amp; & amp; 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 " +
                "'getBeanNamesForType' 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;
  }
/**
   * fill attribute
   *
   * @param beanName the name of the bean
   * @param mbd the bean definition for the bean
   * @param bw the BeanWrapper with bean instance
   */
  @SuppressWarnings("deprecation") // for postProcessPropertyValues
  protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
    ...
    PropertyDescriptor[] filteredPds = null;
    if (hasInstAwareBpps) {
      if (pvs == null) {
        pvs = mbd. getPropertyValues();
      }
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof InstantiationAwareBeanPostProcessor) {
          InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
          // get attribute injection
          PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
          if (pvsToUse == null) {
            if (filteredPds == null) {
              filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
            }
            pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
            if (pvsToUse == null) {
              return;
            }
          }
          pvs = pvsToUse;
        }
      }
    }
    if (needsDepCheck) {
      if (filteredPds == null) {
        filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
      }
      checkDependencies(beanName, mbd, filteredPds, pvs);
    }

    if (pvs != null) {
      applyPropertyValues(beanName, mbd, bw, pvs);
    }
  }
@Override
  public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
      //
      metadata.inject(bean, beanName, pvs);
    }
    catch (BeanCreationException ex) {
      throw ex;
    }
    catch (Throwable ex) {
      throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    return pvs;
  }
...
if (value != null || this.required) {
              this.cachedFieldValue = desc;
              registerDependentBeans(beanName, autowiredBeanNames);
              if (autowiredBeanNames. size() == 1) {
                String autowiredBeanName = autowiredBeanNames. iterator(). next();
                                //The following logic is to get the injection corresponding to the property. beanFactory.isTypeMatch internally calls getSingleton
                if (beanFactory. containsBean(autowiredBeanName) & amp; & amp;
                    beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
                  this.cachedFieldValue = new ShortcutDependencyDescriptor(
                      desc, autowiredBeanName, field. getType());
                }
              }
            }
...

by checking whether a bean with the given name matches the specified type.

protected boolean isTypeMatch(String name, ResolvableType typeToMatch, boolean allowFactoryBeanInit)
      throws NoSuchBeanDefinitionException {

    String beanName = transformedBeanName(name);
    boolean isFactoryDereference = BeanFactoryUtils.isFactoryDereference(name);

    // get cached value
    Object beanInstance = getSingleton(beanName, false);
....
}

At this point, spring will recursively call getSingleton according to the properties of the object, and create it if it does not exist. The whole process is called cyclically. b0e874d408c11c3cba3b21aa7c3a22eb.png

Get bean

Then the obtained bean has actually been instantiated.

The method to get the bean

@Override
  public <T> T getBean(Class<T> requiredType) throws BeansException {
    assertBeanFactoryActive();
    return getBeanFactory().getBean(requiredType);
  }

Core code: used to parse Bean, ResolvableType.forRawClass(requiredType) is used to obtain the ResolvableType object of requiredType, args is the parameter of the constructor, false means that the circular dependency of Bean is not allowed

@Override
  public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException {
    Assert.notNull(requiredType, "Required type must not be null");
    /** Core code: used to parse Bean, ResolvableType.forRawClass(requiredType) is used to obtain the ResolvableType object of requiredType, args is the parameter of the constructor, false means that the circular dependency of Bean is not allowed **/
    Object resolved = resolveBean(ResolvableType. forRawClass(requiredType), args, false);
    if (resolved == null) {
      throw new NoSuchBeanDefinitionException(requiredType);
    }
    return (T) resolved;
  }
...
@Override
  public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {
    String beanName = transformedBeanName(name);
    // Obtain through L3 cache
    Object beanInstance = getSingleton(beanName, false);
    if (beanInstance != null) {
      return (beanInstance instanceof FactoryBean);
    }
    // No singleton instance found -> check bean definition.
    if (!containsBeanDefinition(beanName) & amp; & amp; getParentBeanFactory() instanceof ConfigurableBeanFactory) {
      // No bean definition found in this factory -> delegate to parent.
      return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name);
    }
    return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName));
  }
...

af129a2a20c5d82e8809a10ff56546fd.png

632cc8f1ec661de0d7a82771925f0f35.png

650f54fba1e4163482cfd9658fadccc4.png

/** The core acquisition method of the third-level cache first obtains from the first-level cache (singletonObjects),
   * if empty and not being created
   * The synchronous lock method is obtained from the second-level cache (earlySingletonObjects)
   * The second-level cache acquisition is empty and the provided loading flag is true
   * Acquired through a singleton factory (singletonFactories) and if it is not empty, it will be initialized and placed in the primary and secondary cache
   *
   *
   * return fetch result
   */
  @Nullable
  protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this. singletonObjects. get(beanName);
    if (singletonObject == null & amp; & amp; isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this. singletonObjects) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null & amp; & amp; allowEarlyReference) {
          ObjectFactory<?> singletonFactory = this. singletonFactories. get(beanName);
          if (singletonFactory != null) {
            singletonObject = singletonFactory. getObject();
            this.earlySingletonObjects.put(beanName, singletonObject);
            this. singletonFactories. remove(beanName);
          }
        }
      }
    }
    return singletonObject;
  }

81b7098d5dfb5988741dc3c95050980c.png

86fc8d6ed3b4dd939db8ef2e86e9c4a5.png7304ec9fae1fedeaa7f11f34e92d0c6b.png

Why is there a third-level cache?

Initializing attributes from the source code relies on the second-level cache (earlySingletonObjects) for implementation, then the third-level cache (singletonFactories) mainly solves the problem that the proxy in AOP needs to store the proxy object in the cache in advance, and the proxied object only needs to save the proxy factory It is placed in the third-level cache without instantiation, so here the third-level cache stores the proxy factory, not the instantiated object, and the object is only instantiated when it needs to be called (taken from the first and second levels) ), so this design is more reasonable, the first level is directly the instance, the second level cache solves the object attribute injection at this time, the object is not complete, and the third level stores the agent’s guidance, not the specific implementation. In fact, the final point here is through this proxy Signature callback acquisition results, clear division of labor at each level, is also relatively easy to understand.

Personal understanding: the factory’s g finished products (level 1 cache), semi-finished products (level 2 cache), orders (raw materials) (level 3 cache). 66340195163f783236ff6c2d4e56c39d.png

Last

According to the follow-up source code, it can be clearly understood that the circular dependency of spring is to instantiate the non-lazy-loaded bean in the refresh method when starting and initializing. Of course, if there is a circular dependency injection attribute, the attribute is initialized. Through two Level can solve the problem of dependence, three-level cache (singletonFactories) is used to solve the index location in advance such as AOP proxy. The code is relatively cumbersome, and it is recommended to follow up the debug in depth.

Reference:

https://www.bilibili.com/video/BV18a411P7cq/

https://blog.csdn.net/weixin_43716742/article/details/124448697

https://www.zhihu.com/question/445446018

https://zhuanlan.zhihu.com/p/496273636

https://www.cnblogs.com/daimzh/p/13256413.html