Circular dependency of Spring source code

What is circular dependency?

Very simply, object A depends on object B, and object B depends on object A. For example:

//A relies on B
class A{
  public B b;
}

//B relies on A
class B{
  public A a;
}

If Spring is not considered, circular dependency is not a problem, because it is normal for objects to depend on each other. For example:

A a = new A();
B b = new B();
a.b = b;
b.a = a;

Because in Spring, an object is not simply new, but will go through the life cycle of the Bean. In all cases, the problem of circular dependency occurs because of the life cycle of the Bean. Of course, there are many scenarios of circular dependency in Spring. Some scenarios are solved automatically by Spring, while others need to be solved by programmers. The details are as follows:

1. Single instance cyclic dependency (supported)

Generally speaking, if you ask how to solve circular dependencies within Spring, it must be a scenario in which attributes refer to each other in the default singleton Bean.

During bean instantiation, we use the doGetBean() method

Category: org springframework. beans. factory. support. AbstractBeanFactory

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

		// Instantiate the bean.
		// BeanWrapper is wrapped on the instantiated object
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			// It is possible that other beans created the current Bean before the Bean was created (for example, during dependency injection)
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			// Create an instance, focusing on****
			// 1. Instantiate the instance corresponding to the @ Bean annotation
			// 2. Instantiate a parameterized constructor annotated with @ Autowired
			// 3. Instantiate a parameterized constructor without @ Autowired annotation
			// 4. Instantiate parameterless constructor
			// After instantiation, the instance exists in the heap memory, but the attribute is empty (an incomplete object)
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		// Get the real instance object from the BeanWrapper
		Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}

		// Allow post-processors to modify the merged bean definition.
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
					/**
					 * TODO CommonAnnotationBeanPostProcessor Support @ PostConstruct, @ PreDestroy, @ Resource annotation
					 *     AutowiredAnnotationBeanPostProcessor Support @ Autowired and @ Value annotation
					 *     Collecting annotations: the assembly process of annotations in a class
					 *     This method is a typical application of the BeanPostProcessor interface. The importance level is 5, which must be seen
					 *     Collect annotated properties and methods in the class, wrap them into objects, add the objects to the container, wrap the container into InjectionMetadata objects, and put them into the cache
					 *     The cache is the corresponding relationship between beanName and InjectionMetadata. Then, through this object, you can know which attribute or method has annotation, so as to prepare for the following attribute filling
					 */
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}

		// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		// If it is a singleton and supports circular dependency, and there is a Bean being created in the container, return true (resolve circular dependency)
		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");
			}
			// Circular dependency, add a three-level cache, that is, execute getEarlyBeanReference(): judge whether the current Bean needs AOP
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			/**
			 * (Attribute filling) IOC, DI, the core method of dependency injection, importance: 5,
			 *  It mainly completes the dependency injection of @ Autowired, @ Resource and xml configuration methods. Before that, there were instances in the heap memory, but the attribute was empty
			 */
			populateBean(beanName, mbd, instanceWrapper);
			/**
			 * (Initialization) Bean instantiation + IOC dependency injection is executed after completion. Importance: 5
			 * 	@PostConstruct Annotation method -- > afterpropertieset() method of initializingbean interface -- > init method property, generation entry of AOP proxy object
			 */
			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 circular dependency is supported
		if (earlySingletonExposure) {
			// You can get it from the L2 cache
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				// Compare whether the proxy object is the same as the original object. In most cases, it is the same because it is the original object
				if (exposedObject == bean) {
					// If it is the same, assign the object obtained from the L2 cache to the original object
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					// Which beans depend on beanName? If you find that the bean object corresponding to beanName has changed, an error will be reported
					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 {
			// Class DisposableBeanAdapter when registering Bean destruction
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
		}

		return exposedObject;
	}

Enter the getSingleton() method:

public Object getSingleton(String beanName) {
   return getSingleton(beanName, true);
}

Continue to:

Category: org springframework. beans. factory. support. DefaultSingletonBeanRegistry

	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// Take the instance from the cache according to beanName, and take it from the first level cache first
		Object singletonObject = this.singletonObjects.get(beanName);
		// If the bean is still being created and has not been created, the heap memory is available and the attributes have not been DI dependency injected
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			// Why lock it? Ensure the atomicity of the execution of the following L2 cache and L3 cache methods
			synchronized (this.singletonObjects) {
				// Get from L2 cache
				singletonObject = this.earlySingletonObjects.get(beanName);
				// If not, allow the bean to be exposed in advance
				if (singletonObject == null && allowEarlyReference) {
					// Fetch from L3 cache (single instance factory)
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						// Get the object from the L3 cache and call the getEarlyBeanReference() method. The returned result is the original object or proxy object
						singletonObject = singletonFactory.getObject();
						// Get the results and put them into the L2 cache
						this.earlySingletonObjects.put(beanName, singletonObject);
						// Delete L3 cache (to reduce bug s)
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

In the DefaultSingletonBeanRegistry class, you will find these three maps hanging above the class, which is commonly referred to as the three-level cache.

  • singletonObjects is our most familiar friend, commonly known as "Singleton pool" and "container". It is the place where the singleton Bean is created (L1 cache).
  • earlySingletonObjects is an early reference to a mapped Bean, that is, the Bean in this Map is not complete or even called a "Bean", but an Instance (secondary cache). When there are cyclic dependencies of more than three objects, it is necessary to use the getBean() of the same object.
  • The singleton factories map creates the Bean's original factory (L3 cache).

The L3 cache will only be adjusted once. The last two maps are actually at the "stepping stone" level. They are only used when creating beans. They are cleared after creation.

Why are the last two maps stepping stones? Suppose the Bean finally placed in singletonObjects is a cup of "cool and white" you want. Then Spring prepared two cups, singleton factories and earlySingletonObjects, to "toss" back and forth several times, and put the hot water in singleton objects.

If you can get an instance from the cache, you will return. If you can't get it, you will create an instance. You will enter the following else branch in doGetBean() and enter the getSingleton() method:

Category: org springframework. beans. factory. support. DefaultSingletonBeanRegistry

/**
 * TODO Get the singleton bean. The second parameter ObjectFactory is a functional interface with a getObject() method
 */
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(beanName, "Bean name must not be null");
   synchronized (this.singletonObjects) {
      // If there is in the cache, it returns directly
      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 + "'");
         }

         // Add beanName to the singletonsCurrentlyInCreation Set container. All beans in this set are instantiating beans
         beforeSingletonCreation(beanName);
         boolean newSingleton = false;
         boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
         if (recordSuppressedExceptions) {
            this.suppressedExceptions = new LinkedHashSet<>();
         }
         try {
            // getObject() will be called to the createBean() method of the previous anonymous class; If there is a return value here, it means that the bean has been created successfully
            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;
            }
            // Delete the beanName from the singletonsCurrentlyInCreation container, indicating that the instantiation has been completed
            afterSingletonCreation(beanName);
         }
         if (newSingleton) {
             // Put the successfully created Bean into the L1 cache
            addSingleton(beanName, singletonObject);
         }
      }
      return singletonObject;
   }
}

Store the beanName into the singletonsCurrentlyInCreation (Set) container, identify the role, indicate that the current class is being instantiated, and enter the beforeSingletonCreation() method:

protected void beforeSingletonCreation(String beanName) {
   // Add beanName to the singletons currently in creation (set) container. All beans in this set are instantiating beans
   // This container is used for circular dependency. When the verification bean is being created, the following exception will be thrown
   if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
      throw new BeanCurrentlyInCreationException(beanName);
   }
}

Instantiation has been completed. Delete beanName from singletonsCurrentlyInCreation container and enter afterSingletonCreation():

protected void afterSingletonCreation(String beanName) {
   if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
      throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
   }
}

        singletonObject = singletonFactory.getObject(); If there is a return value here, it means that the bean has been successfully created, and then put it into the L1 cache, delete the L2 and L3 caches, and enter addSingleton():

Category: org springframework. beans. factory. support. DefaultSingletonBeanRegistry

protected void addSingleton(String beanName, Object singletonObject) {
   synchronized (this.singletonObjects) {
      // L1 cache
      this.singletonObjects.put(beanName, singletonObject);
      // L2 cache
      this.singletonFactories.remove(beanName);
      // L3 cache
      this.earlySingletonObjects.remove(beanName);
      // Statistics function when provided to external API s
      this.registeredSingletons.add(beanName);
   }
}

Let's focus on the instantiation of createBean() - > docreatebean ()) - > createbainstance () parameterless constructor. At this time, instance A in heap memory has not yet relied on injection, only A space has been opened up in memory, and the B object it refers to is still null.

There is also an addSingletonFactory() method before createBeanInstance() and populateBean(), which is used to set the three-level cache. The premise is that the singleton is allowed, circular dependency is allowed, and the bean is still being created, because it will be deleted from the container in afterSingletonCreation().

// TODO adds a three-level cache, that is, execute getEarlyBeanReference(), which focuses on understanding and helps to understand circular dependency,
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

It defines an anonymous inner class to obtain the proxy object through the getEarlyBeanReference method. In fact, the underlying layer is to generate the proxy object through the getEarlyBeanReference() method of the AbstractAutoProxyCreator class.

Category: org springframework. beans. factory. support. AbstractAutowireCapableBeanFactory

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		// Get the A object created for the second time in the circular dependency. After getting the A object, it means that the B object needs dependency injection. Then, it also means that the instantiation of the B object is completed
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					// The application of BeanPostProcessor, which embeds a point at a key place, is a decorator design pattern:
					// Get all instances from BeanPostProcessor to traverse, and repeatedly decorate the same object exposedObject,
					// The decoration here is not necessarily modified, but can also be called
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		return exposedObject;
	}

Incidentally, if you need to modify the exposedObject, you can implement the smartinstantiaawarebeanpostprocessor interface and rewrite the getEarlyBeanReference() method. Spring provides us with such an extension with high flexibility.

The getEarlyBeanReference() method will be called further:

Category: org springframework. aop. framework. autoproxy. AbstractAutoProxyCreator

	// Circular dependency will be called here
	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		// Record the AOP of the currently instantiated Bean in this Map
		this.earlyProxyReferences.put(cacheKey, bean);
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

In the whole Spring, AbstractAutoProxyCreator is the only real implementation by default
getEarlyBeanReference method, and this class is used for AOP.
The parent class of AnnotationAwareAspectJAutoProxyCreator is AbstractAutoProxyCreator.
So what exactly is the getEarlyBeanReference method doing? First, get a cachekey, which is
beanName. The beanname and bean (this is the original object) are then stored in earlyProxyReferences and called
wrapIfNecessary performs AOP to obtain a proxy object.

    // If AOP is required for the currently instantiated Bean, a proxy is generated; otherwise, the original object is returned
	@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			// earlyProxyReferences is a concurrent HashMap that stores beans that need AOP
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				// Generate the proxy object of the currently instantiated bean**
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

So when will the getEarlyBeanReference method be called?

Back to the scenario of circular dependency, in the getSingleton() method, the lambda expression will not be executed when storing singletonFactories, that is, the getEarlyBeanReference method will not be executed.

Get an ObjectFactory from singletonFactories according to beanName, and then execute ObjectFactory, that is, execute getEarlyBeanReference method. At this time, you will get A proxy object after AOP of original A object, and then put the proxy object into earlySingletonObjects. Note that the proxy object is not put into singletonObjects at this time, When will it be put into singletonObjects?
At this time, we need to understand the role of earlySingletonObjects. At this time, we only get the proxy object of the original object a, which is not complete. Because the original object a has not been filled with attributes, we can't directly put the proxy object of a into singletonObjects, so we can only put the proxy object into earlySingletonObjects, Assuming that other objects now depend on a, you can get the proxy object of the original object of a from earlySingletonObjects, and it is the same proxy object of A.
After B is created, A continues the life cycle. After A completes attribute injection, A will AOP according to its own logic. At this time, we know that the original object of A has experienced AOP, so for A itself, it will not AOP again. So how to judge whether an object has experienced AOP? Will use the earlyProxyReferences mentioned above to
In the postProcessAfterInitialization method of AbstractAutoProxyCreator, it will judge whether the current beanName is
No. in early proxyreferences, if yes, it means that AOP has been performed in advance and there is no need to perform AOP again.
For A, after the judgment of AOP and the execution of BeanPostProcessor, it is necessary to put the object corresponding to A into singletonObjects, but we know that the proxy object of A should be put into singletonObjects, so at this time, we need to get the proxy object from earlySingletonObjects and then into singletonObjects.
The whole circular dependency is solved.

Let's use a diagram to illustrate the solution process of Spring when circular dependency occurs:

The instantiation order of the two classes is as follows:

 2. Constructor circular dependency (not supported)

@Component
public class CircularRefConA {

    private CircularRefConB circularRefConB;

    // @Lazy
    // The getBean of parameter object B will be triggered
    public CircularRefConA(CircularRefConB circularRefConB) {
//        circularRefConB.getB();
        this.circularRefConB = circularRefConB;
//        circularRefConB.getB();
        System.out.println("============CircularRefConA()===========");
    }

    public void getA(){

    }
}

@Component
public class CircularRefConB {

    private CircularRefConA circularRefConA;
    // @Lazy
    // The getBean of parameter object A will be triggered
    public CircularRefConB(CircularRefConA circularRefConA) {
//        circularRefConA.getA();
        this.circularRefConA = circularRefConA;
//        circularRefConA.getA();
        System.out.println("============CircularRefConB()===========");
    }

    public void getB() {

    }
}

In this case, an error will be reported:

The beforeSingletonCreation() method in getSingleton():

Category: org springframework. beans. factory. support. DefaultSingletonBeanRegistry

protected void beforeSingletonCreation(String beanName) {
   // Add beanName to the singletons currently in creation (set) container. All beans in this set are instantiating beans
   // This container is used in circular dependency to verify whether the bean is being created. If it is already being created, the following exception will be thrown
   if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
      throw new BeanCurrentlyInCreationException(beanName);
   }
}

The exception is thrown here. The circular dependency of the construction method directly reports an error, because even the object cannot be created, let alone the subsequent A object depends on B object. How to solve it: add @ Lazy annotation to the construction method.

3. Prototype circular dependency (not supported)

In the case of prototypes, if both are prototypes, new beans are created every time, which is endless ~ Spring does not support.

Whether a single thread calls getBean() multiple times or multiple threads call getBean() multiple times, the instances obtained each time are different (hashcodes are different), and multiple instances will be created.

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class CircularRefPrototypeB {
    @Autowired
    private CircularRefPrototypeA circularRefPrototypeA;
}

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class CircularRefPrototypeB {
    @Autowired
    private CircularRefPrototypeA circularRefPrototypeA;
}

Usually, you will go to doGetBean() in AbstractBeanFactory class:

// Multiple cases
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);
   }
   // This method is the call entry of the FactoryBean interface
   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, () -> {
          // Put the instance in threadLocal
         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);
   }

A singleton getSingleton() method is missing here. getSingleton() will put the created instance into the first level cache. In the case of multiple instances, just create the instance. After creation, it will not be put into the L3 cache, because the L3 cache is only for single instances.

When creating A new A, it is found that the prototype field B is to be injected, and A new B is created. It is found that the prototype field A is to be injected This is the dolls. StackOverflow or OutOfMemory first? Spring is afraid that it's hard for you to guess, so it throws A beancurrentyincreationexception first.

/**
 * TODO:Deal with the problem of circular dependency in prototype mode
 *  Trigger scenario: in the prototype mode, if there is A B attribute in A and A attribute in B, when dependency injection,
 *  When A has not been created, the creation of B returns to create A again, resulting in circular dependency;
 *  If there is no singletonObjects in the cache, go down,
 *  If the Scope is Prototype, check whether there is circular dependency. If so, an error will be reported directly
 */
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
   throw new BeanCurrentlyInCreationException(beanName);
}

Each invocation of multiple instances is separate. A new instance is created, so it is placed in ThreadLocal.

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
   // There is a value in threadLocal
   Object curVal = this.prototypesCurrentlyInCreation.get();
   return (curVal != null &&
         (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
protected void beforePrototypeCreation(String beanName) {
   Object curVal = this.prototypesCurrentlyInCreation.get();
   if (curVal == null) {
      // Put it in threadLocal for the first time
      this.prototypesCurrentlyInCreation.set(beanName);
   }
   else if (curVal instanceof String) {
      Set<String> beanNameSet = new HashSet<>(2);
      beanNameSet.add((String) curVal);
      beanNameSet.add(beanName);
      this.prototypesCurrentlyInCreation.set(beanNameSet);
   }
   else {
      Set<String> beanNameSet = (Set<String>) curVal;
      beanNameSet.add(beanName);
   }
}

4.@Async asynchronous situation (solved by programmer)

@Service
publicclass AService1 {

    @Autowired
    private BService2 bService2;

    @Async
    public void test1() {
    }
}
@Service
publicclass BService2 {

    @Autowired
    private AService1 aService1;

    public void test2() {
    }
}

Error message:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'aService1': Bean with name 'aService1' has been injected into other beans [bService2] 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.

Reason: when the circular dependency has AOP and the @ Async asynchronous annotation is added to the AService, an error will be reported, because the @ enableasync annotation will register an aysnannotationbeanpostprocessor class in Spring, When the initialization method initializeBean() is executed, its own postProcessAfterInitializeBean() method will be executed after the method of AnnotationAwareAspectJAutoProxyCreator is executed. It will also generate a proxy object itself. As a result, the exposedObject object returned by the initialization method is different from the initial Bean, and it will enter the else method. The exception is thrown from here. Because the object assigned to the attribute is not the same as the object put into the singleton pool, it certainly can't.

Solution: adding @ Lazy annotation to the dependent attribute will directly generate a proxy object assigned to the @ Lazy annotated attribute, so that circular dependency will not occur. The original Bean will not be created until the method of the proxy object is called, but the Bean has been generated when the container is started. You can find the Bean object through beanName, and then call the method directly.

In addition, adding the @ Async annotation to BService will not report an error, because Spring instantiates beans in order, instantiating aservice first, and then BService.

5.DependsOn circular dependency (not supported)

@DependsOn(value = "testService2")
@Service
publicclass TestService1 {

    @Autowired
    private TestService2 testService2;

    public void test1() {
    }
}
@DependsOn(value = "testService1")
@Service
publicclass TestService2 {

    @Autowired
    private TestService1 testService1;

    public void test2() {
    }
}

After the program is started, the execution result:

Circular depends-on relationship between 'testService2' and 'testService1'

Why?

The answer is at org springframework. beans. factory. support. In this code of abstractbeanfactory#dogetbean() method:

// Summary: now you are ready to create a Bean. For singleton beans, this Bean has not been created in the container; For prototype beans, it is necessary to create a new Bean.
			try {
				// Parent child BeanDefinition merge
				RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				// Check whether beandefinition is Abstract
				checkMergedBeanDefinition(mbd, beanName, args);

				// Guarantee initialization of beans that the current bean depends on.
				// Obtain dependent object information (dependent objects must be instantiated first)
				String[] dependsOn = mbd.getDependsOn();
				if (dependsOn != null) {
					for (String dep : dependsOn) {
						// Dep is the dependent object name. Judge whether beanName is dependent on dep. if so, circular dependency occurs
						if (isDependent(beanName, dep)) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
						}
						// If dep is dependent on beanName, store it in dependentBeanMap (DEP is key and beanName is value)
						registerDependentBean(dep, beanName);
						try {
							// Create the dependent Bean*****
							getBean(dep);
						}
						catch (NoSuchBeanDefinitionException ex) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
						}
					}
				}

It will check whether the instance of dependsOn has circular dependency, and throw an exception if there is circular dependency.  

Finally, summarize the L3 cache:

  1. singletonObjects: the singleton pool caches beans that have passed through the full life cycle.
  2. Early singleton objects: the most important thing to solve circular dependency is the L2 cache, which stores a semi-finished product. Cache beans that have not gone through the full life cycle. If a bean has circular dependency, it will put the bean that has not gone through the full life cycle into earlySingletonObjects in advance. If the bean needs to go through AOP, it will put the proxy object into earlySingletonObjects. Otherwise, it will put the original object into earlySingletonObjects. However, It is a proxy object, and the original object represented by the proxy object has not gone through the full life cycle, so we can uniformly consider it as a bean that has not gone through the full life cycle by putting it into earlySingletonObjects.
  3. singletonFactories: what is cached is an ObjectFactory, that is, a lambda expression. During the generation of each bean, after an original object is instantiated, a lambda expression will be exposed based on the original object in advance and saved to the L3 cache. This lambda expression may or may not be used. If the current bean does not have circular dependency, this lambda expression is useless, The current bean executes normally according to its own life cycle. After execution, the current bean is directly put into singletonObjects. If the current bean finds circular dependency during dependency injection (the bean currently being created is dependent on other beans), it gets the lambda expression from the three-level cache and executes the lambda expression to get an object, And put the obtained object into the L2 cache (if the current bean needs AOP, execute the lambda expression to get the corresponding proxy object. If AOP is not required, get an original object directly)).
  4. In fact, there is also a cache, earlyProxyReferences, which is used to record whether an original object has been AOP.

Keywords: Java Spring bean

Added by lmg on Wed, 19 Jan 2022 00:52:16 +0200