How does Spring solve circular dependency

Posted by classic on Sat, 19 Feb 2022 19:39:27 +0100

1. Preface

Cyclic dependence : is N class circular (nested) references.

Generally speaking, N beans refer to each other and finally form A closed loop. It can be represented by A classic diagram as follows (A, B and C all represent objects, and the dotted line represents reference relations):

  • In fact, it can be N=1, that is, the cyclic dependence in the limit case: self dependence
  • The circular reference referred to here is not a circular call between methods, but the interdependence of objects. (circular calls between methods can work normally if there is an exit)

2. Circular dependency of spring bean

When it comes to the circular dependency of Spring beans, some small partners may be unfamiliar. After all, it seems that they have no perception of the concept of circular dependency in the development process. In fact, you have this illusion because you work in the swaddling clothes of Spring, which makes you "relax"~

I firmly believe that my partners must have written the following code in their usual business development:

 
@Service
public class AServiceImpl implements AService {

    @Autowired
    private BService bService;

    ...

}

@Service
public class BServiceImpl implements BService {

    @Autowired
    private AService aService;

    ...

}

This is actually a typical circular dependency scenario in the Spring environment. But obviously, Spring has perfectly solved and avoided the problem in this circular dependency scenario. Therefore, even if we do this circular reference at ordinary times, we can complete our coding journey~

3. Demonstration of three circular dependency scenarios in Spring

In the Spring environment, because the instantiation and initialization of our Bean are handed over to the container, its circular dependency is mainly manifested in the following three scenarios. To facilitate the demonstration, I have prepared the following two classes:

3.1 constructor injection cycle dependency

 
@Service
public class A {

    public A(B b) {
    
    }

}

@Service
public class B {

    public B(A a) {

    }

}

Result: the project failed to start and threw an exception: BeanCurrentlyInCreationException

 
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:339)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)

The constructor injects a circular dependency. This circular dependency method cannot be solved. Only the beancurrentyincreationexception exception can be thrown to indicate the circular dependency. This is also the biggest disadvantage of constructor injection (it has many unique advantages, please find out by yourself)

Root cause: Spring's solution to circular dependency relies on the concept of Bean's "intermediate state", which refers to the state that has been instantiated but not initialized. The constructor is the thing that completes instantiation, so the loop dependency of the constructor cannot be solved~~~

3.2 singleton pattern field attribute injection cyclic dependency

This method is our most commonly used dependency injection method (so you can guess that it will not be a problem):

 
@Service
public class A {

    @Autowired
    private B b;

}


@Service
public class B {

    @Autowired
    private A a;

}

Results: the project was started successfully and can work normally

Note: since the principle of setter method injection is similar to that of field injection, there is no more demonstration here

3.3 prototype mode field attribute injection cyclic dependency

prototype is rarely used at ordinary times, but it is not that it will not be used, so this method also needs to be paid attention to.

 
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class A {

    @Autowired
    private B b;

}


@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class B {

    @Autowired
    private A a;

}

Result: it should be noted that there will be no error when starting in this example (because non singleton beans will not be initialized by default, but will be initialized when used), so it's very simple. We just need to manually getBean() or @ Autowired it in a singleton Bean

 
// Inject in a singleton Bean

@Autowired
private A a;

In this way, the sub start will report an error:

 
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'mytest.TestSpringBean': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a': Unsatisfied dependency expressed through field 'b'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; 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.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596)

at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374)

How to solve???
Some friends may see online that @ Lazy annotation is used to solve the problem:

@Lazy
@Autowired
private A a;

It's responsible to tell you that this can't solve the problem (it may cover up the problem), @ Lazy is just delaying initialization. When you really use it (initialization), the above exception will still be reported.

The Spring circular dependency is summarized as follows:

Situations that cannot be solved:

  • Constructor injection loop dependency
  • prototype pattern field attribute injection cyclic dependency

Situations that can be solved:

  • singleton pattern field attribute injection (setter method injection) cyclic dependency

4. Principle analysis of spring to solve circular dependency

Before that, you need to understand the difference between the so-called reference passing and value passing in java.

Note: seeing this sentence, some friends may want to spray me. java is obviously passed. This is an interview question I recited 100 times when I first learned java. How can I be wrong???
This is the necessity for me to make this statement: man, your statement is correct. There is only value passing in java. However, this article uses reference transmission to help explain. I hope you can understand what I want to express~

The theoretical basis of Spring's circular dependency is based on Java reference passing. When the reference of an object is obtained, the attribute of the object can be set later. (but the constructor must be before obtaining the reference. After all, your reference is generated by the constructor. Can your son be born before your father? Ha ha)

4.1 Spring Bean creation process

First of all, we need to understand the process of creating beans in Spring. I draw its general call stack as follows:

The three core methods for creating beans are explained as follows:

  • createBeanInstance: instantiation, which is actually calling the constructor of the object to instantiate the object
  • populateBean: fill in attributes. This step is mainly to inject the dependent attributes of the bean (@ Autowired)
  • initializeBean: return to some methods such as initMethod and InitializingBean

From the initialization of the singleton Bean, we can see that the cyclic dependency mainly occurs in the second step (populateBean), that is, the processing of field attribute injection.

"Spring 3.4 container"

In the whole declaration cycle of the Spring container, the singleton Bean has one and only one object. It's easy to think of caching to speed up access.
It can also be seen from the source code that Spring makes a lot of use of Cache, and even does not hesitate to use "three-level Cache" in the process of solving the circular dependency problem, which is the subtlety of its design~

In fact, the three-level cache is more like a term in the Spring container factory. It adopts the three-level cache mode to solve the problem of circular dependency. The three-level cache refers to:

 
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

...

// The top-down sub table represents the "three-level cache"

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //L1 cache

private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // L2 cache

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // L3 cache

...


/** Names of beans that are currently in creation. */

// This cache is also very important: it means that the bean will stay in it during the creation process~

// It puts the value at the beginning of Bean creation and moves it out when it is completed~

private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));


/** Names of beans that have already been created at least once. */

// When the Bean is created, it will be marked as this. Note: This is the set set and will not be repeated

// Those created at least once will be put here~~~~

private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));

}

Note: AbstractBeanFactory inherits from DefaultSingletonBeanRegistry

  • singletonObjects: used to store fully initialized bean s. Beans taken from the cache can be used directly
  • earlySingletonObjects: the cache of singleton objects exposed in advance, which stores the original bean objects (not filled with attributes) to solve circular dependencies
  • singletonFactories: the cache of singleton object factory, which stores bean factory objects and is used to solve circular dependencies

Obtain the source code of the singleton Bean as follows:

 
  1. public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

  2. ...

  3. @Override

  4. @Nullable

  5. public Object getSingleton(String beanName) {

  6. return getSingleton(beanName, true);

  7. }

  8. @Nullable

  9. protected Object getSingleton(String beanName, boolean allowEarlyReference) {

  10. Object singletonObject = this.singletonObjects.get(beanName);

  11. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

  12. synchronized (this.singletonObjects) {

  13. singletonObject = this.earlySingletonObjects.get(beanName);

  14. if (singletonObject == null && allowEarlyReference) {

  15. ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);

  16. if (singletonFactory != null) {

  17. singletonObject = singletonFactory.getObject();

  18. this.earlySingletonObjects.put(beanName, singletonObject);

  19. this.singletonFactories.remove(beanName);

  20. }

  21. }

  22. }

  23. }

  24. return singletonObject;

  25. }

  26. ...

  27. public boolean isSingletonCurrentlyInCreation(String beanName) {

  28. return this.singletonsCurrentlyInCreation.contains(beanName);

  29. }

  30. protected boolean isActuallyInCreation(String beanName) {

  31. return isSingletonCurrentlyInCreation(beanName);

  32. }

  33. ...

  34. }

  1. First, get the singletonObjects from the first level cache. (return directly if you get it)
  2. If the object cannot be obtained or is being created (issingletoncurrentyincreation()), it will be obtained from the L2 cache earlySingletonObjects. (return directly if you get it)
  3. If it is still unavailable, singletonFactories (alloweerlyreference = true) can be obtained through getObject(). From the third level cache singletonfactory getObject(). (if it is obtained, it will be removed from singletonFactories and put into earlySingletonObjects. In fact, it is moved from the L3 cache (cut, not copy ~) to the L2 cache.)

The premise of adding singletonFactories L3 cache is that the constructor is executed, so the cyclic dependency of the constructor cannot be solved

getSingleton() gets the singleton object from the Cache. The step analysis shows that Spring's trick to solve the circular dependency lies in the three-level Cache singletonFactories. This Cache is full of ObjectFactory, which is the key to solve the problem.

 
  1. //It can encapsulate the steps of creating objects into ObjectFactory and give it to the custom scope to choose whether to create objects to flexibly implement the scope. See scope interface for details

  2. @FunctionalInterface

  3. public interface ObjectFactory<T> {

  4. T getObject() throws BeansException;

  5. }

Via objectfactory After GetObject (), it is put into the L2 cache earlySingletonObjects. At this time, the object has been instantiated. Although it is not perfect, the reference of the object can be referenced by others.

Let's talk about the L2 cache earlySingletonObjects. When will the data in it be added and removed???

Add: there is only one place to add data to it, that is, move it from the L3 cache in getSingleton()
Remove: addSingleton, addSingletonFactory and removeSingleton. From the semantics, we can see that when adding singleton and singleton factory ObjectFactory, the corresponding cache values in the L2 cache will be deleted. They are mutually exclusive.

4.3 source code analysis

The Spring container will place each Bean identifier being created in a "currently created Bean pool". The Bean identifier will remain in this pool during the creation process, and the created beans will be cleared from the currently created Bean pool.

The "currently created Bean pool" refers to the singletons currently created collection mentioned above.

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
	...
	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
		...
		// Eagerly check singleton cache for manually registered singletons.
		// Get it first. If it is not null, the cache will go here~~
		Object sharedInstance = getSingleton(beanName);
		...
		// If you don't just check the type, mark that the Bean has been created ~ ~ added to the cache, which is the so-called currently created Bean pool
		if (!typeCheckOnly) {
			markBeanAsCreated(beanName);
		}
		...
		// Create bean instance.
		if (mbd.isSingleton()) {
		
			// This getSingleton method is not the interface method of SingletonBeanRegistry. It belongs to a public overloaded method of the implementation class DefaultSingletonBeanRegistry~~~
			// It is characterized by the implementation of singletonfactory getObject(); Execute beforeSingletonCreation(beanName) before and after; And after singleton creation (beanname);  
			// That is to ensure that the bean is put into the cache pool being created during the creation process. You can see that it actually creates the bean and calls our createBean method~~~~
			sharedInstance = getSingleton(beanName, () -> {
				try {
					return createBean(beanName, mbd, args);
				} catch (BeansException ex) {
					destroySingleton(beanName);
					throw ex;
				}
			});
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
		}
	}
	...
}
 
// The interface method where the abstract method createBean is located belongs to the implementation of the abstract parent class AbstractBeanFactory in this abstract class
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
	...
	protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
		...
		// Create a Bean object and wrap the object in the BeanWrapper
		instanceWrapper = createBeanInstance(beanName, mbd, args);
		// Then, the original object of the Bean (non proxy ~ ~ ~) from the Wrapper. At this time, the Bean will have an address value and can be referenced~~~
		// Note: This is the original object, which is very important
		final Object bean = instanceWrapper.getWrappedInstance();
		...
		// earlySingletonExposure is used to indicate whether to "expose" the reference of the original object in advance, which is used to solve the circular dependency.
		// For a singleton Bean, this variable is generally true, but you can also turn off the circular reference through the attribute allowCircularReferences = false
		// Issingletoncurrent yincreation (beanname) indicates that the current bean must be created
		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");
			}
			// As mentioned above, call this method to put it into an ObjectFactory, and the L2 cache will be deleted accordingly
			// Function of getEarlyBeanReference: call smartinstantiaawarebeanpostprocessor The getEarlyBeanReference () method, otherwise nothing will be done
			// That is to give the caller an opportunity to implement the logic that exposes the application of the bean~~~
			// For example, the logic of AOP can be implemented in getEarlyBeanReference() ~ ~ ~ refer to the automatic proxy creator AbstractAutoProxyCreator, which implements this method to create proxy objects
			// If you do not need to execute the logic of AOP, you can directly return to Bean
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}
		Object exposedObject = bean; //exposedObject is the final returned object
		...
		// Fill in and solve @ Autowired dependency~
		populateBean(beanName, mbd, instanceWrapper);
		// Execute initialization callback methods~~~
		exposedObject = initializeBean(beanName, exposedObject, mbd);
		
		// Early singleton exposure: if your bean is allowed to be exposed early, that is, it can be referenced circularly, then it will be checked here
		// This code is very important ~ ~ ~ ~ ~ but most people ignore it
		if (earlySingletonExposure) {
			// At this time, there must be no data in the L1 cache, but there is no data in the L2 cache earlySingletonObjects at this time
			//Note: Note: if the second parameter is false, it means that you will not check in the L3 cache again~~~
 
			// Here is a very clever point: because all kinds of instantiated and initialized postprocessors above are executed. If you execute this sentence above
			//  ((ConfigurableListableBeanFactory)this.beanFactory).registerSingleton(beanName, bean);
			// Then the reference of earlySingletonReference obtained here will eventually be the final return of the Bean you put in manually, which perfectly realizes the "stealing the day" and is especially suitable for the design of middleware
			// We know that after executing this doCreateBean, executing addSingleton() is actually adding yourself again * * emphasize again, and realize the perfect change of heaven and earth**
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
			
				// This means that if the exposedObject still changes after initializeBean(), you can return with confidence
				// initializeBean will call the post processor. At this time, a proxy object can be generated. At this time, the two brothers will not be equal. Go else to judge
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				} 
 
				// Allowerawinjectiondespitewrapping is false by default
				// hasDependentBean: if it has a dependent bean, it needs to continue verification ~ ~ ~ ~ (if there is no dependency, let it go ~)
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					// Get the beans it depends on ~ ~ ~ ~ the following will traverse one by one~~
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					
					// So Bean, check it one by one
					// Removesignletonifcreatedfortypecheckonly is shown below in AbstractBeanFactory
					// Simply put, if it determines that the dependentBean is not being created, it will remove it from all caches ~ ~ ~ and return true
					// Otherwise (for example, it is actually being created), it will return false and enter our if ~ to indicate the so-called real dependency
					//(explanation: you really need to instantiate a dependency before you can instantiate your dependency.)
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
 
					// If there is a real dependency, report an error (don't wait until the memory is removed, it's very unfriendly) 
					// This exception is a beancurrentyincreationexception. Pay attention to the error log to locate the error~~~~
					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.");
					}
				}
			}
		}
		
		return exposedObject;
	}
 
	// Although it is a remove method, its return value is also very important
	// The only place this method calls is the last check of the circular dependency~~~~~
	protected boolean removeSingletonIfCreatedForTypeCheckOnly(String beanName) {
		// If the bean is not being created, such as ForTypeCheckOnly, remove it
		if (!this.alreadyCreated.contains(beanName)) {
			removeSingleton(beanName);
			return true;
		}
		else {
			return false;
		}
	}
 
}

​​​​​​ 

Here's an example: for example, for field attribute dependency injection, when populateBean, it will first complete the instantiation and initialization process of the bean it depends on to inject, and finally return to this process to continue processing. Therefore, there is no problem with Spring's processing.

Here's a small detail:

if (exposedObject == bean) {
    exposedObject = earlySingletonReference;
}

In this sentence, if exposedObject == bean means that the final returned object is the original object, which means that it has not been represented to him in populateBean and initializeBean, then don't say anything. exposedObject = earlySingletonReference, and finally return the reference in the L2 cache~

4.4 process summary

Here, take the interdependence injection of classes A and B as an example to express the trend of key codes:

1. The entrance is to instantiate and initialize the singleton Bean A. AbstractBeanFactory.doGetBean("a")

protected <T> T doGetBean(...){
	... 
	// The tag beanName a has been created at least once ~ ~ ~ it will remain in the cache and will not be removed (unless an exception is thrown)
	// See cache set < string > alreadycreated = collections newSetFromMap(new ConcurrentHashMap<>(256))
	if (!typeCheckOnly) {
		markBeanAsCreated(beanName);
	}
 
	// At this time, a does not exist in any level 1 cache and is not being created, so null is returned here
	// If it is not null here, you can take it from the cache (mainly dealing with FactoryBean and BeanFactory)
	Object beanInstance = getSingleton(beanName, false);
	...
	// This getSingleton method is critical.
	//1. Dimension a is being created~
	//2. Call singletonobject = singletonfactory getObject(); (in fact, the createBean() method is called), so this step is the most critical
	//3. When the instance has been created, a will be removed from the created cache
	//4. Execute addSingleton() to add it. (Note: the interface method of the registered bean is registerSingleton, which depends on the addSingleton method)
	sharedInstance = getSingleton(beanName, () -> { ... return createBean(beanName, mbd, args); });
}

​​​​​​​2. Let's go to the most complex abstractautowirecapablebeanfactory Createbean / docreatebean() phase to create an instance of A

protected Object doCreateBean(){
	...
	// Using the constructor / factory method, instanceWrapper is a BeanWrapper
	instanceWrapper = createBeanInstance(beanName, mbd, args);
	// The Bean here is "original Bean", that is, the a instance object here: A@1234
	final Object bean = instanceWrapper.getWrappedInstance();
	...
	// Whether to expose in advance (circular dependency is allowed) now A is allowed here
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
	
	// If exposure is allowed, bind A to ObjectFactory and register it in the three-level cache 'singletonFactories' to save it
	// Tips: here, the getEarlyBeanReference method of the post processor will be prompted, and the automatic proxy Creator will create the proxy object here (note that the execution time is when the L3 cache is executed)
	if (earlySingletonExposure) {
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}
	...
	// exposedObject is the final returned object. Here is the original object bean, that is A@1234 , the following will be useful
	Object exposedObject = bean; 
	// to A@1234 Property is assigned, and @ Autowired works here~
	// Therefore, getBean("b") will be called here, and so will repeat the above steps to create an instance of class B
	// Here we assume that B has been created as B@5678
	
	// It should be noted that when populateBean("b") depends on beanA, so calling getBean("a") at this time will eventually call getSingleton("a"),
	//At this time, the getEarlyBeanReference method mentioned above will be executed. This also explains why @ Autowired is a proxy object rather than an ordinary object
	
	populateBean(beanName, mbd, instanceWrapper);
	// Instantiation. Two methods of the post processor BeanPostProcessor will be executed here
	// Note here: it is possible for postProcessAfterInitialization() to return a proxy object, so that the exposedObject is no longer the original object. Special note~~~
	// For example, the AsyncAnnotationBeanPostProcessor that processes @ Aysnc generates proxy objects at this time (if there are pits, please use @ Aysnc carefully)
	exposedObject = initializeBean(beanName, exposedObject, mbd);
 
	... // So far, it is equivalent to A@1234 Instantiation and initialization have been completed (all attributes have been assigned ~)
	// In this step, I understand it as verification: Verification: verify whether there is a circular reference problem~~~~~
 
	if (earlySingletonExposure) {
		// Note that the second parameter passed here is false, which means you don't need to call the getObject() method again from singletonFactories in the L3 cache~~~
		// As mentioned above, when B initializes, it will trigger the objectfactory of A GetObject (), so a here is already in the L2 cache earlySingletonObjects
		// Therefore, an instance of a is returned here: A@1234
		Object earlySingletonReference = getSingleton(beanName, false);
		if (earlySingletonReference != null) {
		
			// This equation indicates that if the exposedObject is not represented again, it is equal here
			// Obviously, the exposedObject of our a object here has not been represented, so if will go in~
			// This situation is now over~~~
			if (exposedObject == bean) {
				exposedObject = earlySingletonReference;
			}
	
			// Continue to take A as an example. For example, the method is annotated with @ Aysnc annotation, and the exposedObject is A proxy object at this time, so it will come here
			//hasDependentBean(beanName) must be true, because getDependentBeans(beanName) gets the dependency of ["b"]
			else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
				String[] dependentBeans = getDependentBeans(beanName);
				Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
 
				// A@1234 It depends on ["b"], so check b here
				// If the actual dependent bean: actualDependentBeans is not empty, an exception will be thrown to prove the circular reference~
				for (String dependentBean : dependentBeans) {
					// The judgment principle is: if b has not been created at this time, this alreadyCreated. Contains (beanname) = true indicates that this bean has been created, and false is returned
					// If the bean is not in the alreadyCreated cache, it means it has not been created (in fact, only CreatedForTypeCheckOnly is this warehouse)
					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.");
				}
			}
		}
	}
}

Since the steps of key code parts are not easy to split, in order to express more concretely, use the following diagram to help the partners understand:

5. Summary

As an example, class A and class B above use attribute field to inject circular dependency. The text steps of the whole process are summarized as follows:

  1. Use context GetBean (A.class) aims to obtain the singleton A in the container (if A does not exist, the creation process of A Bean will be followed). Obviously, obtaining A for the first time does not exist, so take the road of creating A~
  2. Instantiate A (note that this is just instantiation) and put it into the cache (at this time, A has been instantiated and can be referenced)
  3. Initialize A: @ Autowired dependency injection B (at this time, you need to get B from the container)
  4. In order to complete dependency injection B, we will find B in the container through getBean(B). But at this time, B does not exist in the container, so it goes to the road of creating B~
  5. Instantiate B and put it into the cache. (B can also be referenced at this time)
  6. Initialize B, @ Autowired dependency injection A (at this time, you need to get A from the container)
  7. Important here: when initializing B, we will call getBean(A) to find A in the container. As we have said above, at this time, because A has been instantiated and put into the cache, we can see that there is already A reference in the cache, so getBean(A) can return normally
  8. B is initialized successfully (at this time, A has been injected successfully and A reference has been held successfully). Return (note that return here is equivalent to returning the top getBean(B) code and returning to the process of initializing A ~).
  9. Because instance B has returned successfully, A is initialized successfully
  10. So far, B holds A that has completed initialization, and A holds B that has completed initialization, perfect~

From a higher perspective, this is the whole process of Spring dealing with circular dependency. It is hoped that this macro level summary will be more helpful for our friends to understand the principle of Spring to solve circular dependency, and also explain why the constructor circular dependency is difficult to work.

Topics: Java Spring Back-end