spring has its own solution for circular dependencies between beans. The key point is level three cache. Of course, this solution cannot solve all problems, it can only solve the circular dependence of non-constructors in Bean singleton mode.
We will start from the initialization order of A->B->CA, which means that instances of B are needed in the Bean of A, instances of C are needed in the Bean of B, instances of A are needed in the Bean of C, and instances of A are needed in the Bean of C. Of course, this need is not a dependency like constructor. Once the prerequisites are available, we can start. There is no doubt that we will initialize A first. The initialization method is org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
protected <T> T doGetBean( final String name, final Class<T> requiredType, 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); // Focus 1 if (sharedInstance != null && args == null) { if (logger.isDebugEnabled()) { if (isSingletonCurrentlyInCreation(beanName)) { logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.debug("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 assumed 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 (args != null) { // Delegation to parent with explicit args. return (T) parentBeanFactory.getBean(nameToLookup, args); } else { // No args -> delegate to standard getBean method. return parentBeanFactory.getBean(nameToLookup, requiredType); } } 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 (dependentsOn != null) { for (String dependsOnBean : dependsOn) { if (isDependent(beanName, dependsOnBean)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dependsOnBean + "'"); } registerDependentBean(dependentsOnBean, beanName); getBean(dependentsOnBean); } } // Create bean instance. if (mbd.isSingleton()) { //Concern 2 sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { 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, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { 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 && bean != null && !requiredType.isAssignableFrom(bean.getClass())) { try { return getTypeConverter().convertIfNecessary(bean, requiredType); } catch (TypeMismatchException ex) { if (logger.isDebugEnabled()) { logger.debug("Failed to convert bean '" + name + "' to required type [" + ClassUtils.getQualifiedName(requiredType) + "]", ex); } throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } return (T) bean; } This method is very long, let's talk about it little by little. Let's look at our focus first. Object sharedInstance = getSingleton(beanName ) obtains a singleton object from the collection of singletons based on the name. Let's look at this method, and it is finally org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
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) { ObjectFactory<?> singletonFactory = this.singletonFactory.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactory.remove(beanName); } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); } Everyone must pay attention to this method, it is very important. We mentioned the level 3 cache at the beginning, and one of the points of use is here. Which level 3 cache is it? singletonObjects of the first level cache is placed with instantiated singletonObjects. The second level earlySingletonObjects store singleton objects that are exposed in advance (not fully assembled). The third-level singletonFactories store the object factory of the object to be instantiated. After explaining the level 3 cache, let’s take a look at the logic. The first time I come in this.singletonObjects.get(beanName) returns null. Then isSingletonCurrentlyInCreation determines whether the data can be obtained in the secondary cache.
public boolean isSingletonCurrentlyInCreation(String beanName) { return this.singletonsCurrentlyInCreation.contains(beanName); } Does the beanName be included in the singletonsCurrentlyInCreation Set contain the incoming BeanName? There is no place to set it before, so it definitely does not include it. Therefore, this method returns false, and the subsequent process will not be left. The getSingleton method returns null.
Let's look at the focus 2. It is also a getSingleton , but it is the real process of creating a bean. We can see that an anonymous ObjectFactory object is passed in. The getObject method called createBean, the real method of creating a bean. Of course we can put it aside and continue to look at our getSingleton method
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "'beanName' 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 the 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<Exception>(); } 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 != NULL_OBJECT ? singletonObject : null); } } The first sentence of this method Object singletonObject = this.singletonObjects.get(beanName) fetches data from the first-level cache, which is definitely null. The beforeSingletonCreation method is then called.
protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } } Among them is the process of adding beanName to the singletonsCurrentlyInCreation Set. This set is very important and will be used later. Then, call the getObject method of singletonFactory to perform the real creation process. Let’s take a look at the real creation process mentioned above createBean The core logic inside it is doCreateBean .
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) { // 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 != null ? instanceWrapper.getWrappedInstance() : null); Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null); // Allow post-processors to modify the merged bean definition. synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. //Concern3 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isDebugEnabled()) { logger.debug("Eagerly cached bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); } }); } // Initialize the bean instance. Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); if (exposedObject != null) { 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<String>(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; } createBeanInstance creates an object using reflection. Let's take a look at the judging point 3 earlySingletonExposure attribute value. One of the judgment points isSingletonCurrentlyInCreation(beanName)
public boolean isSingletonCurrentlyInCreation(String beanName) { return this.singletonsCurrentlyInCreation.contains(beanName); } I found that the singletonsCurrentlyInCreation Set is used. The BeanName has been filled in in the above steps, so it can be found. Therefore, the earlySingletonExposure property is determined to be true in combination with other conditions, and the following process is added addSingletonFactory . Here is an object factory corresponding to the BeanName(A). The implementation of its getObject method is achieved through getEarlyBeanReference method. First, let's take a look at the implementation of addSingletonFactory
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.singletonFactory.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } } Storing data to the third level cache singletonFactories, clearing the second level cache data based on beanName. There is a very important point here, which is to set the value into the third-level cache, which is the core point of Spring's processing of circular dependencies. The getEarlyBeanReference method is an implementation of getObject. It can be simply considered that it returns an object instance that is filled with A. After setting the Level 3 cache, the process of filling the properties of the A object begins. The following description does not have source code prompts, just a brief introduction.
When filling A, I found that a type B bean is needed, so I continued to call the getBean method to create it. The memory process is exactly the same as A above. Then I went to the process of filling a type C bean. The same call getBean(C) to execute. When filling a property A, I called getBean(A). Let's continue from here that I called Object sharedInstance = getSingleton(beanName), the same code, but the processing logic is completely different.
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) { ObjectFactory<?> singletonFactory = this.singletonFactory.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactory.remove(beanName); } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); } Still, the object is obtained from singletonObjects cannot be obtained. Because A is in the singletonsCurrentlyInCreation Set, he enters the following logic, and takes it from the second-level cache earlySingletonObjects , but still has not been found. Then he finds the corresponding object factory from the third-level cache singletonFactories Call getObject method to obtain the instance object of A that has not been completely filled, and then deletes the third-level cache data, fills the second-level cache data, and returns this object A. C depends on A's instance filling, although this A is incomplete. No matter how C-style filling is completed, you can put C in the first-level cache singletonObjects and clean the data of the second-level and third-level caches at the same time. In the same process, if C depends on B is filled, B is filled. Similarly, if B depends on A is filled, A is filled. This is how Spring solves circular references.
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.