This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

This article looks at the source code and ideas for spring loop dependencies.

It is recommended to refer to this article and follow the source code to learn.

1. What are circular dependencies

Here’s an example of what a cyclic dependency is:

There are two classes, A and B, with the following code:

/** * loop dependency A * @date: 2020/12/24 * @author weirx * @version 3.0 */ public class A {public B getB() {return B; } public void setB(B b) { this.b = b; } private B b; }Copy the code
/** * loop dependency B * @date: 2020/12/24 * @author weirx * @version 3.0 */ public class B {public A getA() {return A; } public void setA(A a) { this.a = a; } private A a; }Copy the code

A has attribute B, and B has attribute A, which are interdependent as shown in the figure below:

What problems do circular dependencies cause?

When learning the bean life cycle, the bean has to go through the initialization process after instantiation, and the initialization needs to assign values to the bean properties. However, when the instance of A is initialized, B has not been instantiated, so the attribute assignment of A fails.

2. Spring Solutions

Students, the content of related must know spring solution for the circular dependencies is to use level 3 cache, there is an important kind of DefaultSingletonBeanRegistry sping source code, in this defines three levels of cache, as shown below:

/** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); */ Private final Map<String, ObjectFactory<? >> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);Copy the code

The essence of this is that bean instantiation and initialization are handled separately, with caching enabling early exposure of semi-finished (uninitialized) products.

Spring has set and constructor injection methods. Do these two methods solve the problem of loop dependency?

The main method:

import org.springframework.context.support.ClassPathXmlApplicationContext; /** * main * @date: Public class TestSpring {public static void main(String[] args) {public static void main(String[] args) { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml"); }}Copy the code

The set method:

/** * loop dependency A * @date: 2020/12/24 * @author weirx * @version 3.0 */ public class A {public B getB() {return B; } public void setB(B b) { this.b = b; } private B b; }Copy the code
/** * loop dependency B * @date: 2020/12/24 * @author weirx * @version 3.0 */ public class B {public A getA() {return A; } public void setA(A a) { this.a = a; } private A a; }Copy the code

The XML file:

    	<bean id="a" class="com.demo.A">
    		<property name="b" ref="b"></property>
    	</bean>
    	<bean id="b" class="com.demo.B">
    		<property name="a" ref="a"></property>
    	</bean>
Copy the code

The result of execution, which can be solved by the test, is cyclic dependent:

Constructor mode:

/** * loop dependency A * @date: 2020/12/24 * @author weirx * @version 3.0 */ public class A {private B B; public A(B b) { this.b = b; }}Copy the code
/** * loop dependent B * @date: 2020/12/24 * @author weirx * @version 3.0 */ public class B {private A A; public B(A a) { this.a = a; }}Copy the code

The XML configuration:

<bean id="a" class="com.demo.A">
        <constructor-arg ref="b"></constructor-arg>
    </bean>
    <bean id="b" class="com.demo.B">
        <constructor-arg ref="a"></constructor-arg>
    </bean>
Copy the code

If the fault cannot be resolved, you will get the following error message:

十二月 25, 2020 9:38:00 上午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'a' defined in class path resource [spring-beans.xml]: Cannot resolve reference to bean 'b' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b' defined in class path resource [spring-beans.xml]: Cannot resolve reference to bean 'a' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'a' defined in class path resource [spring-beans.xml]: Cannot resolve reference to bean 'b' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b' defined in class path resource [spring-beans.xml]: Cannot resolve reference to bean 'a' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:342)
	at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:113)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:704)
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:196)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1356)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1203)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:556)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:897)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
	at com.demo.TestSpring.main(TestSpring.java:14)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b' defined in class path resource [spring-beans.xml]: Cannot resolve reference to bean 'a' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:342)
	at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:113)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:704)
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:196)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1356)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1203)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:556)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:330)
	... 17 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:330)
	... 29 more
Disconnected from the target VM, address: '127.0.0.1:60287', transport: 'socket'

Process finished with exit code 1

Copy the code

3. Source tracking

Here’s a flow chart of my source tracking process:

The key point in the figure above is that in Spring source code, it is common to see getBean, doGetBean, etc. In the process of solving the loop dependency, the important process is shown in the figure below:

Code tracing begins:

Classes A and B adopt set mode

Execute the main method:

/** * main * @date: Public class TestSpring {public static void main(String[] args) {public static void main(String[] args) { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml"); }}Copy the code

Tracking the refresh method finishBeanFactoryInitialization method, tracking the last line of code inside directly the beanFactory. PreInstantiateSingletons () :

public void preInstantiateSingletons() throws BeansException { if (logger.isTraceEnabled()) { logger.trace("Pre-instantiating singletons in " + this); } List<String> beanNames = new ArrayList<>(this.beandefinitionNames); String beanName: String beanName: String beanName: BeanNames) {/ / get the definition of a bean RootBeanDefinition bd = getMergedLocalBeanDefinition (beanName); // Bean is not abstract & singleton & lazy loading 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 getBean(beanName); } } } // Trigger post-initialization callback for all applicable beans... 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(); }}}}Copy the code

The code ignores unimportant flow and the breakpoint goes straight to the getBean method:

Public Object getBean(String name) throws BeansException {return doGetBean(name, NULL, null, false); }Copy the code

Trace into the doGetBean, two key points, check the registered cache (getSingleton(beanName)), and create the bean instance, all annotated in the following code to focus on:

protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = transformedBeanName(name); Object bean; 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 { 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 ()) {// Get bean instance from cache, using function interface (ObjectFactory is a function interface, when calling its default method, 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(); if (! Stringutils.haslength (scopeName)) {throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'"); } 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 ScopeNotActiveException(beanName, scopeName, 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; }Copy the code

Let’s look at each of these two key points and see what they do. GetSingleton = null; getSingleton = null; getSingleton = null;

@Nullable protected Object getSingleton(String beanName, Boolean allowEarlyReference) {/ / see if there are any current level 1 cache the bean instance of the Object singletonObject = this. SingletonObjects. Get (beanName); // Check whether the current object in the cache is null. And is currently being created beans if (singletonObject = = null && isSingletonCurrentlyInCreation (beanName)) {synchronized (this singletonObjects) {/ / from the second level cache the bean instance singletonObject = this. EarlySingletonObjects. Get (beanName); If (singletonObject == null && allowEarlyReference) {// Get the bean instance ObjectFactory<? > singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory ! = null) {/ / perform in functional interface methods singletonObject = singletonFactory. GetObject (); / / add bean to the second level cache enclosing earlySingletonObjects. Put (beanName singletonObject); / / remove the l3 cache this. SingletonFactories. Remove (beanName); } } } } return singletonObject; }Copy the code

If the instance does not exist in the query cache, it needs to be created. Part of the code block to create the instance is as follows. Here we focus on the getSingletion method and here is a lambda expression:

// Create bean instance if (mbd.issingleton ()) {// Get bean instance from cache, using function interface (ObjectFactory is function interface, when calling its default method, 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); }Copy the code

Let’s go to the getSingleton method. The second argument, ObjectFactory<? >, this object is a functional interface. When the default method inside the executor, getObject, calls createBean, the content of the previous getSingleton lambda expression; The following method after obtaining level cache, found did not take to the bean instance, after some judgment performed behind singletonFactory. GetObject (), enter the createBean phase:

public Object getSingleton(String beanName, ObjectFactory<? > singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); Synchronized (enclosing singletonObjects) {/ / get beans from level 1 cache 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<>(); } the try {/ / call the ObjectFactory default method, perform createBean 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; }}Copy the code

Enter the createBean method, where the main method is doCreateBean:

@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); } the try {/ / let BeanPostProcessors have the opportunity to return to an agent 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); } 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); }}Copy the code

To enter doCreateBean, there are a number of key steps: 1) Create a bean instance by reflection. 2) Set the level 3 cache, remove the level 2 cache, key is beanName,value is a reference to the current bean (getEarlyBeanReference method), this reference method is important to remember, is the key of AOP. 3) Initialization of the bean: populateBean and initializeBean are populated

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) {// createBeanInstance instanceWrapper = createBeanInstance(beanName, MBD, args); } / / get the bean instance Object Object bean. = instanceWrapper getWrappedInstance (); Class<? > beanType = instanceWrapper.getWrappedClass(); if (beanType ! = NullBean.class) { mbd.resolvedTargetType = beanType; } / / Allow the post - processors to modify the merged bean definition. / / Allow BeanDefinitionPostProcessors modified combined 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 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"); } // set the level 3 cache, again using the functional interface ObjectFactory<? > addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; Try {// Fill the property populateBean(beanName, MBD, instanceWrapper); // Initialize the bean 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 " + "'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; }Copy the code

Add level 3 cache:

protected void addSingletonFactory(String beanName, ObjectFactory<? > singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (! This. SingletonObjects. Either containsKey (beanName)) {/ / set level 3 cache, Key is beanName, a reference value is the current bean (getEarlyBeanReference method) enclosing singletonFactories. Put (beanName singletonFactory); / / delete the second level cache this. EarlySingletonObjects. Remove (beanName); this.registeredSingletons.add(beanName); }}}Copy the code

Enter populateBean. This class is used to assign a value to the bean’s properties. This class is mainly used to get the property value and its method to set the bean property value applyPropertyValues:

@SuppressWarnings("deprecation") // for postProcessPropertyValues 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 InstantiationAwareBeanPostProcessors the opportunity to modify the // state of the bean before properties are set. This can be used, for example, // to support styles of field injection. if (! mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) { if (! bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { return; PVS = (mbd.haspropertyValues ()? mbd.getPropertyValues() : null); int resolvedAutowireMode = mbd.getResolvedAutowireMode(); if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs  = new MutablePropertyValues(pvs); // Add property values based on autowire by name if applicable. if (resolvedAutowireMode == AUTOWIRE_BY_NAME) { autowireByName(beanName, mbd, bw, newPvs); } // Add property values based on autowire by type if applicable. if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) { autowireByType(beanName, mbd, bw, newPvs); } pvs = newPvs; } boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); boolean needsDepCheck = (mbd.getDependencyCheck() ! = AbstractBeanDefinition.DEPENDENCY_CHECK_NONE); PropertyDescriptor[] filteredPds = null; if (hasInstAwareBpps) { if (pvs == null) { pvs = mbd.getPropertyValues(); } for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) { PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } pvsToUse = bp.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) {// Set propertyValues (beanName, MBD, bw, PVS); }}Copy the code

ResolveValueIfNecessary (PV, originalValue) is the key method to obtain property values:

protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { if (pvs.isEmpty()) { return; } if (System.getSecurityManager() ! = null && bw instanceof BeanWrapperImpl) { ((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext()); } MutablePropertyValues mpvs = null; List<PropertyValue> original; if (pvs instanceof MutablePropertyValues) { mpvs = (MutablePropertyValues) pvs; if (mpvs.isConverted()) { // Shortcut: use the pre-converted values as-is. try { bw.setPropertyValues(mpvs); return; } catch (BeansException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Error setting property values", ex); } } original = mpvs.getPropertyValueList(); } else { original = Arrays.asList(pvs.getPropertyValues()); } TypeConverter converter = getCustomTypeConverter(); if (converter == null) { converter = bw; } BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter); List<PropertyValue> deepCopy = new ArrayList<>(original.size())); // create a deepCopy that resolves references to any value. boolean resolveNecessary = false; for (PropertyValue pv : original) { if (pv.isConverted()) { deepCopy.add(pv); } else {// Get propertyName String propertyName = pv.getName(); Object originalValue = pv.getValue(); if (originalValue == AutowiredPropertyMarker.INSTANCE) { Method writeMethod = bw.getPropertyDescriptor(propertyName).getWriteMethod(); if (writeMethod == null) { throw new IllegalArgumentException("Autowire marker for property without write method: " + pv); } originalValue = new DependencyDescriptor(new MethodParameter(writeMethod, 0), true); } / / analytical values inside a reference Object resolvedValue = valueResolver. ResolveValueIfNecessary (pv, originalValue); Object convertedValue = resolvedValue; boolean convertible = bw.isWritableProperty(propertyName) && ! PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName); if (convertible) { convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter); } // Possibly store converted value in merged bean definition, // in order to avoid re-conversion for every created bean instance. if (resolvedValue == originalValue) { if (convertible) { pv.setConvertedValue(convertedValue); } deepCopy.add(pv); } else if (convertible && originalValue instanceof TypedStringValue && ! ((TypedStringValue) originalValue).isDynamic() && ! (convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) { pv.setConvertedValue(convertedValue); deepCopy.add(pv); } else { resolveNecessary = true; deepCopy.add(new PropertyValue(pv, convertedValue)); } } } if (mpvs ! = null && ! resolveNecessary) { mpvs.setConverted(); } // Set our (possibly massaged) deep copy. try { bw.setPropertyValues(new MutablePropertyValues(deepCopy)); } catch (BeansException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Error setting property values", ex); }}Copy the code

Enter the resolveValueIfNecessary(pv, originalValue) method. Since we are referencing beans within beans, the value of the bean property is RunTimeBeanPreference, which is the runtime bean reference, We only need to focus on the first few lines of this method:

@Nullable public Object resolveValueIfNecessary(Object argName, @Nullable Object value) { // We must check each value to see whether it requires a runtime reference // to another bean To be resolved. // We have to check each value, If (value instanceof RuntimeBeanReference) {RuntimeBeanReference ref = (RuntimeBeanReference) value; Return resolveReference(argName, ref); }...}Copy the code

GetBean = this.beanFactory.getBean(resolvedName) retrieves the bean, but this time it retrieves the instance of B in the property.

@Nullable private Object resolveReference(Object argName, RuntimeBeanReference ref) { try { Object bean; Class<? > beanType = ref.getBeanType(); if (ref.isToParent()) { BeanFactory parent = this.beanFactory.getParentBeanFactory(); if (parent == null) { throw new BeanCreationException( this.beanDefinition.getResourceDescription(), this.beanName, "Cannot resolve reference to bean " + ref + " in parent factory: no parent factory available"); } if (beanType ! = null) { bean = parent.getBean(beanType); } else { bean = parent.getBean(String.valueOf(doEvaluate(ref.getBeanName()))); } } else { String resolvedName; if (beanType ! = null) { NamedBeanHolder<? > namedBean = this.beanFactory.resolveNamedBean(beanType); bean = namedBean.getBeanInstance(); resolvedName = namedBean.getBeanName(); } else { resolvedName = String.valueOf(doEvaluate(ref.getBeanName())); GetBean = this.beanFactory.getBean(resolvedName); } this.beanFactory.registerDependentBean(resolvedName, this.beanName); } if (bean instanceof NullBean) { bean = null; } return bean; } catch (BeansException ex) { throw new BeanCreationException( this.beanDefinition.getResourceDescription(), this.beanName, "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex); }}Copy the code

The next step is the same, go to doGetBean to get the bean, find none, start creating an instance of B, put the instance of B into the third level cache, and then initialize the instance of B. Here you can pay attention to the code, initialize B, assign attribute A of B, need to get the instance of A, An instance of A is created for the first time and placed in the third level cache. The code to cache A is as follows:

@Nullable protected Object getSingleton(String beanName, Boolean allowEarlyReference) {/ / see if there are any current level 1 cache the bean instance of the Object singletonObject = this. SingletonObjects. Get (beanName); // Check whether the current object in the cache is null. And is currently being created beans if (singletonObject = = null && isSingletonCurrentlyInCreation (beanName)) {synchronized (this singletonObjects) {/ / from the second level cache the bean instance singletonObject = this. EarlySingletonObjects. Get (beanName); If (singletonObject == null && allowEarlyReference) {// Get the bean instance ObjectFactory<? > singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory ! = null) {/ / perform in functional interface methods singletonObject = singletonFactory. GetObject (); / / add bean to the second level cache enclosing earlySingletonObjects. Put (beanName singletonObject); / / remove the l3 cache this. SingletonFactories. Remove (beanName); } } } } return singletonObject; }Copy the code

In fetching instance A above, A is removed from the third level cache and placed in the second level cache, and removed from the third level cache. The key of the level 2 cache is beanName and the value is semi-finished. At this time, B has successfully obtained the semi-finished product of A. Then the initialization of B has been completed and placed in level 1 cache:

protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); }}Copy the code

After b is initialized, the next step is to continue to initialize A. If the duplicate code is no longer pasted, it will eventually put a in the level 1 cache and clear the a in the level 2 cache.

After the initialization of A, the instantiation of B begins. At this point, both a and B have been instantiated and initialized in the level-1 cache. Therefore, both the instantiation of B and the initialization of b attribute A can be obtained directly from the level-1 cache. That’s how Spring solves loop dependencies.

The process is very long, it is recommended to read with the source code, otherwise it is very boring and difficult to read.

Conclusion thinking

Analyze the changes in the three-level cache

According to the above source code process, the entire level 3 cache change process is as follows:

According to the figure above, during the instantiation of A and B, the cache changes step by step according to the above steps. When the cache of A or A is moved from the lower level to the higher level, the cache of the lower level is cleared, and the instance object is always implemented in the cache of only one level.

What is the point of the three-level cache?

Level 2 cache: store semi-finished objects Level 3 cache: store getEarlyBeanReference method

In fact, we can find that only level 1 and level 2 caches are involved in circular dependencies. If we change the code to not use level 3 caches, we can also solve the problem of circular dependencies.

So what does level 3 cache do? Let’s briefly follow the code of the getEarlyBeanReference method.

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (! MBD. IsSynthetic () && hasInstantiationAwareBeanPostProcessors ()) {/ / traverse beanPostProcessor for (SmartInstantiationAwareBeanPostProcessor bp : GetBeanPostProcessorCache (.) smartInstantiationAware) {/ / get the exposed object exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }Copy the code

Trace the getEarlyBeanReference(exposedObject, beanName) method:

@Override public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); / / add a proxy cache this. EarlyProxyReferences. Put (cacheKey, beans); return wrapIfNecessary(bean, beanName, cacheKey); }Copy the code

WrapIfNecessary (bean, beanName, cacheKey); createProxy;

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } / / if there is one notice, to create a proxy Object [] specificInterceptors = getAdvicesAndAdvisorsForBean (bean. GetClass (), beanName, null); if (specificInterceptors ! = DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); CreateProxy (bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); // Set the proxy type this.proxytypes. put(cacheKey, proxy.getClass()); return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }Copy the code

Go to createProxy and focus directly on the last line:

protected Object createProxy(Class<? > beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) { if (this.beanFactory instanceof ConfigurableListableBeanFactory) { AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); } ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); if (! proxyFactory.isProxyTargetClass()) { if (shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } else { evaluateProxyInterfaces(beanClass, proxyFactory); } } Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); proxyFactory.addAdvisors(advisors); proxyFactory.setTargetSource(targetSource); customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy); if (advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); } return proxyFactory.getProxy(getProxyClassLoader()); }Copy the code

Tracking proxyFactory. GetProxy (getProxyClassLoader ()) :

public Object getProxy(@Nullable ClassLoader classLoader) {
		return createAopProxy().getProxy(classLoader);
	}
Copy the code

Tracing getProxy(classLoader) reveals that the interface has two implementation classes, both of which are proxies that implement AOP, exposing the role of the three-level cache. Is the essence of implementing AOP.

Debug trace:

The XML configuration aop

<bean id="a" class="com.demo.A"> <property name="b" ref="b"></property> </bean> <bean id="b" class="com.demo.B"> <property name="a" ref="a"></property> </bean> <bean id="c" class="com.demo.C"></bean> <aop:config> <aop:aspect id="c" ref="c"> <aop:pointcut id="method" expression="execution(* com.demo.*.*(..) )" /> <aop:before pointcut-ref="method" method="before" /> <aop:after pointcut-ref="method" method="after" /> </aop:aspect> </aop:config>Copy the code

Class C:

/** * C * @date: 2020/12/25 * @author weirx * @version 3.0 */ public class C {private A A; private B b; public A getA() { return a; } public void setA(A a) { this.a = a; } public B getB() { return b; } public void setB(B b) { this.b = b; } private void before(){ System.out.println("this is before"); } private void after(){ System.out.println("this is after"); }}Copy the code

Run code debug to getEarlyBeanReference, as shown in the following figure.

When the proxy is created, the currently exposed object is programmed with CGLIB’s proxy object:

That’s it. That’s the end of the whole loop.