Climbing the Spring Pearl mulama peak: preInstantiateSingletons method, L3 cache, circular dependency

1) Spring L3 cache and circular dependency

This article will take the DefaultListableBeanFactory#preInstantiateSingletons method as the starting point, and mainly sort out the problems related to L3 cache and circular dependency.

The following is a brief review of the process between. For details, see another article: Spring launch process

Previous review

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh();
}

Also use a diagram to describe the process

preInstantiateSingletons

@Override
public void preInstantiateSingletons() throws BeansException {
    if (logger.isTraceEnabled()) {
        logger.trace("Pre-instantiating singletons in " + this);
    }

    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

    //Initialize all non lazy loaded singleton beans
    for (String beanName : beanNames) {
        RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
            if (isFactoryBean(beanName)) {
                Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
                if (bean instanceof FactoryBean) {
                    FactoryBean<?> factory = (FactoryBean<?>) bean;
                    boolean isEagerInit;
                    if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                        isEagerInit = AccessController.doPrivileged(
                            (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
                            getAccessControlContext());
                    }
                    else {
                        isEagerInit = (factory instanceof SmartFactoryBean &&
                                       ((SmartFactoryBean<?>) factory).isEagerInit());
                    }
                    if (isEagerInit) {
                        getBean(beanName);
                    }
                }
            }
            else {
                getBean(beanName);
            }
        }
    }

    //Callback interface triggered at the end of the singleton pre instantiation phase during BeanFactory boot. This interface can be implemented by a singleton bean,
    //In order to perform some initialization after the normal singleton instantiation and avoid the side effects of unexpected early initialization (such as the call from ListableBeanFactory.getBeansOfType).
    for (String beanName : beanNames) {
        Object singletonInstance = getSingleton(beanName);
        if (singletonInstance instanceof SmartInitializingSingleton) {
            SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
            if (System.getSecurityManager() != null) {
                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                    smartSingleton.afterSingletonsInstantiated();
                    return null;
                }, getAccessControlContext());
            }
            else {
                smartSingleton.afterSingletonsInstantiated();
            }
        }
    }
}
  1. Iterate over all BeanDefinitionNames collections and initialize all non lazy loaded singleton classes one by one.

  2. BeanDefinitionNames is a collection that is registered at the same time as the BeanDefinitionMap is registered. This collection is mainly used here to iteratively initialize spring beans, and it is also convenient for some methods similar to obtaining BeanDefinitionName.

  3. During initialization, the FactoryBean is also judged, because the default FactoryBean is lazy initialization by default. If you implement SmartFactoryBean and set isEagerInit to true, it means that this class needs urgent initialization and will immediately start initializing the class returned by getObject in FactoryBean.

  4. About getMergedLocalBeanDefinition and getBean: Spring initializes the class through getBean and adds it to the spring container (that is, the first level cache of spring: in the singleton pool). During initialization, it needs the relevant information of the current class: BeanDefinition, which can have parent-child relationship (it is not true inheritance, but BeanDefinition can set the parent BeanDefinition), so spring obtains the complete information of the current class through getMergedLocalBeanDefinition.

getBean→doGetBean

Continue to make method calls in getBean, and finally enter doGetBean,

protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {
				...
 			    ...
				// Create Bean
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							//Create Bean
							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);
				}	
    			...
 			    ...

	}

First, briefly summarize the function of the omitted Code:

  • Try to get from the singleton pool (L1 cache). If you can get it, return directly.
  • Check the @ DependsOn annotation. If the current class depends on the class marked by @ DependsOn, initialize the dependent class first.
  • Marks the status of the current class
  • Validity verification of various parameters
  • Related processing for prototype type beans

Finally, getSingleton is called to initialize the Bean. First sort out the concepts related to the next analysis.

Automatic loading and cyclic dependency

In Spring, we inject one Spring Bean into another through @ Autowired and @ Resource. This process is called automatic loading.

So how does Spring store both beans in the singleton pool and complete the reference? If it is simple to save first and then take, it will certainly not work. Because of special circumstances:

That is, in class A, class B depends on Class A according to class B. For simple consideration, load a → judge dependency B → load B → judge dependency a → load a

According to the general idea, the above situation will occur, resulting in a dead cycle. Spring supports circular dependencies to some extent. The corresponding scheme is the introduction of three-level cache.

L3 cache

The DefaultSingletonBeanRegistry on the parent chain of DefaultListableBeanFactory defines three cache pools:

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
  • singletonObjects: Singleton pool, a spring container in the general sense. Spring stores the initialized spring beans in this pool;
  • earlySingletonObjects: stores singleton classes exposed in advance and proxied,
  • singletonFactories: stores ObjectFactory, and the getObject method of ObjectFactory defines how to get early Bean references.

getSingleton

Now enter the code analysis: getSingleton

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) {
            // If the corresponding resource appears implicitly during bean creation (that is, if it is found that it has been added into the singleton pool at this time, an exception will be thrown)
            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) {
            //If a new Bean instance is obtained (proxy and auto loading have been completed), it is added to the singleton pool
            addSingleton(beanName, singletonObject);
         }
      }
      return singletonObject;
   }
}

When calling the getSingleton method in doGetBean, the lambda feature is used as the input parameter. First, observe getSingleton row by row.

The main code is before single ton creation (bean name). This line of code is important: register the singleton as currently being created.

Then, call the getObject method of ObjectFactory, that is, the content previously defined by lambda: return createBean(beanName, mbd, args)

createBean

Click into the createBean method.

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;

  //Infer the beanClass of the current Beandefinition
   Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
   if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
      mbdToUse = new RootBeanDefinition(mbd);
      mbdToUse.setBeanClass(resolvedClass);
   }

   // Preparation method rewriting includes the processing of lookup method and replaced method, which will not be expanded in this article
   try {
      mbdToUse.prepareMethodOverrides();
   }
   catch (BeanDefinitionValidationException ex) {
      throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
            beanName, "Validation of method overrides failed", ex);
   }

   try {
      // The post processor is called for the first time: before initializing the Bean, the instantiaawarebeanpostprocessor,
      // The corresponding class class is obtained according to the BeanDefinition
      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 {
      //Create Bean
      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);
   }
}

The first post processor call is completed in the createBean. The post processor is not expanded in this article, and the doCreateBean method is called.

The doCreateBean method has a long code and is an important part of spring's handling of cyclic dependencies. We split it up for analysis.

doCreateBean

Infer constructors and create with reflection

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

		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
            //Remove from an uncompleted FactoryBean instance
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			//Instantiate an object (only generate an object, not add it to the spring singleton pool, that is, it does not become a spring bean at present),
			//And call the post processor determineconstructors frombeanpostprocessors for the second time
 			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}
    
 		... 
     	...  
     	...   
}

First, call the post processor through the createBeanInstance method to infer its constructor, and create the instance corresponding to BeanDefinition.

  • Note that only the class object is created at this time, but it has not been added to the Spring singleton pool, that is, it has not been added to the Spring container.
  • It can be understood that only the declaration of class object is completed at this time: similar to a = null

The principle of Spring to solve circular dependency is that java classes can be declared first and then instantiated

Add ObjectFactory to level 3 cache

Continue analyzing doCreateBean

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    	throws BeanCreationException {
        ...
        ...
        //Allow post processor to redefine beanDefinition
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
					//Call the post processor MergedBeanDefinitionPostProcessors for the third time
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}

		//Judge whether circular dependency is allowed. It is allowed by default. Generally, only the value of allowCircularReferences can be modified through code
		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");
			}
			// getEarlyBeanReference contains another call to the post processor, SmartInstantiationAwareBeanPostProcessor 
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}
}

The post processor is called twice, and whether the current project allows circular dependency is determined according to earlySingletonExposure.

earlySingletonExposure is judged by three values:

  • Is the currently created class a singleton class
  • Whether allowCircularReferences is true, that is, whether circular dependencies are allowed at present. Note that this parameter can be manually modified by the programmer, that is, Spring allows the programmer to decide whether circular dependencies are allowed in the program
  • Issingletoncurrentyincreation (beanname): the current class needs to be in the creation state. It has been set to the creation state in beforeSingletonCreation(beanName) of getSingleton above

Attribute population: populateBean

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

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        //Fill in the attribute (i.e. automatic injection) to complete the fifth and sixth post processor calls
        // InstantiationAwareBeanPostProcessor.after
        // hasInstantiationAwareBeanPostProcessors
        populateBean(beanName, mbd, instanceWrapper);
        //Complete the seventh and eighth post processor calls,
        //Judge whether the current Bean is BeanNameAware, BeanClassLoaderAware and beanfactory aware, and set relevant contents respectively
        //applyBeanPostProcessorsAfterInitialization
        //applyBeanPostProcessorsAfterInitialization
        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);
        }
    }
}

populateBean method completes property filling and follows up.

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
   if (bw == null) {
      if (mbd.hasPropertyValues()) {
         throw new BeanCreationException(
               mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
      }
      else {
         // Skip property population phase for null instance.
         return;
      }
   }

  //Give any instantiaawarebeanpostprocessors the opportunity to modify the state of the bean before setting the properties. For example, this can be used to support field injection styles.
  //The extension point calls the post processor again,
   boolean continueWithPropertyPopulation = true;

   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         if (bp instanceof InstantiationAwareBeanPostProcessor) {
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
               continueWithPropertyPopulation = false;
               break;
            }
         }
      }
   }

   if (!continueWithPropertyPopulation) {
      return;
   }
   //If the post processor modifies the Beandefinition, judge whether the related configuration of the attribute value has been modified
   //If it is modified, the loading is directly completed in the way of byName or byType
   PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

   if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME || mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {
      MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
      // Add property values based on autowire by name if applicable.
      if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME) {
         autowireByName(beanName, mbd, bw, newPvs);
      }
      // Add property values based on autowire by type if applicable.
      if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {
         autowireByType(beanName, mbd, bw, newPvs);
      }
      pvs = newPvs;
   }

   boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
   boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

   [] filteredPds = null;
   if (hasInstAwareBpps) {
      if (pvs == null) {
         pvs = mbd.getPropertyValues();
      }
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         if (bp instanceof InstantiationAwareBeanPostProcessor) {
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            //The post processor is used to process the acquisition of attributes
            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) {
      //Complete the property filling according to PropertyValues
      applyPropertyValues(beanName, mbd, bw, pvs);
   }
}

The postprocessor is called again in the populateBean. spring provides an extension point here to allow developers to modify the state of the Bean at the stage of property filling (using the postprocessor). In addition, the property values may be loaded directly according to byName and byType.

We know that @ Autowired and @ Resource can be used when using automatic loading. The functions of these two annotations are also different. Then the way of parsing should be different.

Here, a commonplace question is raised:

@Relationship and difference between Autowired and @ Resource

From the functional level and the simplest level:

  • @Autowired and @ Resource are annotations that complete automatic loading
  • @Autowired loads byType injection by default. If there are multiple results in byType, use the class marked by @ Primary. If you want to use byName injection, you need to use @ qualifier annotation
  • @The Resource is injected by byName by default. If it does not match, it will fall back to the original type for matching. And @ Resource can support byName and byType at the same time

From the analytical and deeper level:

  1. When spring completes the property filling (automatic loading), it uses the post processor to parse, and will call the postProcessProperties method of the post processor.
  2. The post processor for parsing @ Autowired and @ Resource is also different. This is easy to understand. After all, there are differences in the use of the two. Naturally, it is impossible to use the same set of code to complete the parsing.

@Resource: use CommonAnnotationBeanPostProcessor post processor to parse

@Autowired: use Autowired annotation beanpostproceour post processor to parse

As mentioned earlier, to complete automatic loading normally, there should be the following steps: load A → judge dependency B → load B → judge dependency A → load A

spring completes the above steps in the two post processors mentioned above. We analyze the situation that A depends on B and B depends on A.

The post processor gets or loads properties

Take autowiredannotationbeanpostproceour as an example (assuming that the member variable B in A is @ Autowired Setter injection), jump to the key code as follows:

DefaultListableBeanFactory#doResolveDependency

if (instanceCandidate instanceof Class) {
   instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}

...
...
...
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
    throws BeansException {
    return beanFactory.getBean(beanName);
}    

At this time, when autowiredannotationbeanpostproceour is loaded internally, it is found that A depends on B, and B is obtained through getBean.

Note that B is not initialized at this time, that is, it does not exist in the Spring singleton pool.

Then, after calling getBean, B will also perform the loading step of A above:

  1. The inference constructor creates B and encapsulates it in BeanWrapper
  2. Add ObjectFactory of B to level 3 cache
  3. Property filling, and autowiredannotationbeanpostproceour gets the property of B.

Here comes the contradiction: at this time, when the attribute of B is judged to be A, A has not completed the initialization. Do you want to create A again, which undoubtedly leads to an endless loop.

Let's take a look at how Spring handles:

//Try to get
Object sharedInstance = getSingleton(beanName);
@Override
@Nullable
public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}
...
...
...    
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //Attempt to fetch from the L1 cache singleton pool
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            //Fetch from L2 cache
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                //If the L2 cache is not fetched, it is fetched from the L3 cache
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    //Move L3 cache to L2 cache
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

When calling getBean, getSingleton will be called to try to obtain. When obtaining A in the post processor of B, it will enter the method.

There are three important points: creating state, third level cache (ObjectFactory), alloweerlyreference (whether to allow getting early references)

this.singletonObjects.get(beanName): try to fetch from the L1 cache singleton pool. At this time, A is not created, so naturally it cannot be obtained

this.earlySingletonObjects.get(beanName): first judge whether it is in the creating state. If so, it can be fetched from the L2 cache.

  • As mentioned earlier, A was set as being created in beforeSingletonCreation(beanName) of getsingleton (string beanname, objectfactory <? > singletonfactory) at the beginning of getBean

At this time, A still cannot be obtained and does not exist in the L2 cache.

this. singletonFactories. Get (beanname): alloweerlyreference is true. At this time, try to get it from the L3 cache. Call the third level cache objectfactory GetObject () method. Successfully obtained A and moved the L3 cache to L2 cache.

At this time, B is successfully created and returns to the creation process of A again.

Call initializeBean

populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
  • invokeAwareMethods: if the current class is BeanNameAware, BeanClassLoaderAware, beanfactory aware, set relevant properties; Call initialization method
  • Call the post processor twice

Judge the Bean version problem

Normally, it's over here. However, spring made additional judgments. It is also the ending problem of circular dependency.

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {
    ...
    ...
    ...    
  	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				// Allowerawinjectiondespitewrapping: in the case of circular reference, whether to resort to injecting the original bean instance, even if the injected bean is finally wrapped
				// If the currently created bean appears in the L2 cache, it proves that there is a circular reference
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					//Gets the bean on which the current bean depends
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							//If the corresponding dependency has been created
							actualDependentBeans.add(dependentBean);
						}
					}
					//There are dependencies that have been created, that is, the current bean has been used by the bean that references it.
					// That is, the current bean has been loaded, but the bean has been modified. (for example, the post processor of initializeBean re creates new, or another agent is generated)
					// Prove that the initial current bean has a version difference
					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.");
					}
				}
			}
		}  
}
  1. The first is to judge whether circular dependency is supported
  2. If it is supported, try to get the current object and call getSingleton(beanName, false). Note that the second parameter alloweerlyreference is false, which indicates that it is only fetched from the L2 cache (at this time, this class is still being created, and there is no possibility in the L1 cache, so it is actually fetched from the L2 cache)

Why should I get it from the L2 cache?

  • Under normal circumstances, there is only one way for spring L2 cache to be added, that is, move from L3 cache, call ObjectFactory#getObject in L3 cache, and finally call getEarlyBeanReference. In this case, aop proxy may be performed, that is, the objects that finally arrive in the L2 cache are proxied (if this class needs to be proxied)

So when will the L3 cache be moved to the L2 cache?

  • The answer is that when circular dependency occurs, the old examples A and B need to get A when initializing B, and A is being created. At this time, it needs to get it from the L3 cache.

Therefore, the reason for fetching from the L2 cache is to verify whether the current class has circular dependencies.

Then the function of the above long code is obvious:

Use A and B as examples

  1. Verify whether the currently created A object has been injected into dependencies by other objects.
  2. If so, has the current class A been proxied? If so, is the A object in B the same as the object after initializeBean?
  3. If so, go straight back. If not, judge whether to throw an exception according to the value of allowrawujectiondespitewrapping.

Generally speaking, it is to ensure whether to add the current object in the singleton pool in the case of circular reference. In fact, it is to solve the problem of aop agent.

Go back to the getSingleton method and add it to the singleton pool

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 + "'");
				}
                //Mark the current class as being created
				//Set the current initialization class to the set. If it cannot be added, an error will be reported directly (set cannot be added repeatedly)
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<>();
				}
				try {
					//The newly created object is created through the createBean method
					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) {
					//Clear the L2 and L3 caches and add the current instance to the L1 cache singleton pool
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

Good guy, after a long call stack, I almost forgot that all the above code just called getsingleton #singletonfactory Just GetObject ().

Singletonobject = singletonfactory The singleObject obtained by GetObject () is already a fully created object.

Call the addSingleton method and put it directly into the spring cache.

Note: addSingleton will clear the values in the L2 and L3 caches.

  • If there is no circular dependency, there will always be a cache of the current beanName in the L3 cache.
  • Or similar to initializing B, when B obtains a, it can obtain it from the L2 cache of A. However, B itself always has a copy in the L3 cache. In this case, there is also a copy in the L3 cache
  • If circular dependency occurs, it exists in the L2 cache (because A is referenced during initialization and jumps from the L3 cache to the L2 cache)

ok, initialization is complete

2) Other issues

Two special variables that developers can set

  • Alloweerlyreference: whether to get the reference in advance (from the L3 cache) when circular dependency occurs

  • Allowerawinjectiondespitewrapping: do you still want to add the current object in the singleton pool when circular dependency occurs

Both variables can be set as follows:

@Configuration
@ComponentScan("com.sulin")
@EnableAspectJAutoProxy
public class AppConfig {

}

class StartApplication {
	public static void main(String[] args) throws InterruptedException {
        //Register BeanDefinition, set two variables, and refresh manually
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
        AbstractAutowireCapableBeanFactory beanFactory = (AbstractAutowireCapableBeanFactory) ac.getBeanFactory();
        beanFactory.setAllowCircularReferences(false);
        beanFactory.setAllowRawInjectionDespiteWrapping(false);
        ac.register(AppConfig.class);
        ac.refresh();
    }
}

Why do I need L3 cache? Is L2 cache OK?

Answer: Yes, but it's not necessary.

  • L2 cache here refers to L1 cache and L3 cache, i.e. singleton pool and ObjectFactory cache.
  • Indeed, L1 + L3 cache is enough to solve circular dependency. The L2 cache is just the objects obtained from the L3 cache ObjectFactory.
  • If you don't save it to the L2 cache, you can get the object directly from the L3 cache each time. Even if there are AOP agents. Just do it again then.

In fact, in earlier versions of spring, it was the L2 cache. The middle layer is added to the later version.

The getEarlyBeanReference of the three-level cache contains the post processor proxy (AOP), and a layer is added in the middle to improve the efficiency. There is no need to call the code of the post processor every time. The design is also more scientific.

3) Process case summary

Simply sort out the overall process when there is cyclic dependency and when there is no cyclic dependency.

Circular dependency exists: for example, AB interdependence

  1. When getting A through getBean, add the ObjectFactory related to A to the level 3 cache, and set the current state of A to being created;
  2. The constructor infers through the Beandefinition, creates the Java object of A through reflection, and encapsulates it into the WrappedInstance;
  3. Fill in the attribute of A, find that B object needs to be injected (judged according to @ Resource or @ Autowired), and try to obtain B;
  4. When getting B through getBean(B), add the ObjectFactory related to B into the L3 cache and set the current state of B to being created
  5. The constructor infers through the Beandefinition, creates the Java object of B through reflection, and encapsulates it into the WrappedInstance;
  6. Fill in the attribute of B, find that the B object needs to be injected (judged according to @ Resource or @ Autowired), and try to obtain A;
  7. At this time, because a is in the creating state and alloweearlyreference is true, a is taken from the L3 cache (at this time, if a needs an agent, it will call the post processor for AOP related operations) and moved to the L2 cache, and a is moved from the L3 cache to the L2 cache. B successfully obtains the reference of A.
  8. After initialization of B, clean up the resources in the getSingleton method, add the new B object to the L1 cache, and remove B from the L3 cache;
  9. Back to A, A successfully obtains B through getBean(B)
  10. After initialization of A, clean up the resources in the getSingleton method, add the new A object to the L1 cache, and remove B from the L2 cache

flow chart

Keywords: Java Spring Cache

Added by nadnad on Wed, 15 Dec 2021 22:42:13 +0200