Scene reappearance
1. First of all, the project needs to turn on the asynchronous switch of spring and add @EnableAsync to the application main class
2. Create an asynchronous class MessageService containing the @Async method:
@Service public class MessageService { @Resource private TaskService taskService; @Async public void send(){ (); } }
3. Create another normal class TaskService to form a circular reference relationship with the asynchronous class (note that MessageService and TaskService are in the same package, and the order is the default, so the MessageService will be scanned first and then the TaskService will be scanned):
@Service public class TaskService { @Resource private MessageService messageService; public void shit(){ (); } }
4. Successfully reported an error when starting the springboot project
Causes of the problem
Before analyzing the reasons, we need to know two important points in advance:
- Spring's aop proxy (including @Transactional transaction proxy) is created in the applyBeanPostProcessorsAfterInitialization method in the initializeBean after the populateBean method of AbstractAutowirreCapableBeanFactory, and a proxy object is created in a specific postprocessor (if @Autowired is an AnnotationAwareAspectJAutoProxyCreator)
- However, 1. The proxy process described in this class will only be executed if this class does not involve circular references, that is, 90% is satisfied, and the circular reference will be handled in a special way, that is, creating a proxy object in advance.
For example: Class T is a class containing the @Transactional method, which belongs to the object that needs to be proxied, and depends on A through @Resource (or @Autowired). Class A also injects T in the same way, and class T starts the instantiation process before Class A. Then the simple instantiation process is:
- After T's BeanDefinition is obtained by spring, it instantiates a T object (original object, not a proxy object) according to the constructor, wraps it into objectFactory and puts it into singletonFactories (level three cache) and then executes the populateBean method to start the process of injecting properties. The CommonAnnotationBeanPostProcessor will be used to perform the T's attribute injection step by using CommonAnnotationBeanPostProcessor (@Resource uses this postprocessor, @Autowired uses AutowiredAnnotationBeanPostProcessor) to perform the T's attribute injection step, traversing the attributes dependent in T
- It is found that T depends on A, and will first go to the first to third level caches of beanFactory and query the A object through A's beanName. If it is not found, that is, A has not been instantiated yet, then A will be used as the instantiation target and repeat a. Step: wrap the instantiated object A into objectFactory and put it into singletonFactories, and then execute populateBean on A to inject the attributes.
- Iterate through the properties of A and finds that A depends on T, and then tries to get an instance of T in beanFactory. It finds that there is an objectFactory of T in the third-level cache, so the execution method attempts to get an instance of T. However, this objectFactory does not simply return the object, but when it was wrapped, the getEarlyBeanReference method of AbstractAutowireCapableBeanFactory is written into the getObject
- In the getEarlyBeanReference method, all the SmartInstantiationAwareBeanPostProcessor subtype postprocessor will be traversed and the corresponding getEarlyBeanReference method will be executed. At this time, the proxy process mentioned in point 1 will be advanced, that is, through AnnotationAwareAspectJAutoProxyCreator (subclass of SmartInstantiationAwareBeanPostProcessor), it will create a proxy object and put it in the secondary cache earlySingletonObjects, and then the proxy object passes (default form) into A, and then the loop reference processing of ordinary aop objects is completed.
Analysis of the reasons for the occurrence of circular citation exceptions in the title of this article
The class containing the @Async method is similar to the class of @Transactional and will also be replaced with a new proxy class. However, unlike ordinary aop, @Async will not getEarlyBeanReference The logic of creating a proxy is executed in the stage (the reason for this is not carefully analyzed for the time being), but is delayed to the initializeBean step (that is, 90% of the proxy situation mentioned in 1.), which will cause TaskService to inject the proxy object of the final MessageService. It is obvious that such a result is unreasonable. At the code level, in the AbstractAutowireCapableBeanFactory of spring, there is such a piece of code that is easily overlooked between initializeBean and putting the bean into the first-level cache, which is used to control the correctness of the final circular reference result:
// Whether to allow early exposure can be understood as whether circular references are allowedif (earlySingletonExposure) { //Travel the first to third level caches and get the bean Object earlySingletonReference = getSingleton(beanName, false); //If the object in the cache is not empty if (earlySingletonReference != null) { //exposedObject is an object after executing initializeBean, and the bean is the original object created through the constructor // If the two are equal, set exposedObject to the object in cache if (exposedObject == bean) { exposedObject = earlySingletonReference; } //If the two are not the same object, and the injection of the native object is not allowed (default false), and the current beanName is depended by other beans. else if (! && hasDependentBean(beanName)) { // Get all objects that depend on the beanName String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(); for (String dependentBean : dependentBeans) { //If this object is already in level 1 cache, it will be added to actualDependentBeans, that is, the bean that depends on the object is a bean that has completed the entire process and will not have the chance to go back to the redone bean if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { (dependentBean); } } //Finally determine whether actualDependentBeans is empty, and throw an exception of loop reference if it is not empty. if (!()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + (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."); } } } }
- We combine this code to analyze the @Async circular reference scenario:
- First look at line 4. First of all, at this time, it must have not entered the first level cache. We know that @Async does not execute the proxy in getEarlyBeanReference, so the earlySingletonReference obtained in line 4 is the original object of the messageService
- Enter line 9 and judge that exposedObject == bean. Since @Async's proxy process occurs in initializeBean, exposedObject is a proxy object, and bean is the original object that is directly instantiated through the constructor, so it is definitely not equal.
- Enter line 12, allowRawInjectionDespiteWrapping defaults to false, and messageService is referenced by TaskService. Therefore, hasDependentBean (beanName) is true, and 14-line code blocks will be entered.
- The key is to judge 18 lines of ! removeSingletonIfCreatedForTypeCheckOnly (dependentBean), the code of this method is:
protected boolean removeSingletonIfCreatedForTypeCheckOnly(String beanName) { //If the bean is not completely created, return true, otherwise return false if (!(beanName)) { removeSingleton(beanName); return true; } else { return false; } }
Here we will return to what was mentioned when the scene reappears:
3. Note that MessageService and TaskService are in the same package, and the order is the default, so the MessageService will be scanned first and then the TaskService will be scanned.
- Since the messageService is scanned first, the instantiation process of TaskService will be performed in the populateBean of the messageService. The TaskService does not know that the messageService is a class that needs proxy. Therefore, after injecting an unproxy messageService, the initializeBean and subsequent initialization operations are performed with peace of mind, and then marked as successfully created and loaded into the first-level cache.
- That is to say, at this time spring determines that TaskService is an object that has been completely instantiated and initialized. Therefore, the removeSingletonIfCreatedForTypeCheckOnly method will return false, then the 18 line returns true, so the TaskService will be added to the actualDependentBeans, and finally a BeanCurrentlyInCreationException exception is thrown.
- Simply put, spring believes that if a bean is inconsistent before and after the initializeBean, and a beanA that has been fully initialized injects this not fully initialized beanB, the beanA will no longer have the chance to change the injected dependency in the spring process, so an exception will be thrown.
- If you instantiate the TaskService first and then instantiate the MessageService, there will be no problem (if you don’t believe it, you can change the TaskService to ATaskService), because if you don’t find the MessageService exposed in advance when instantiating the TaskService, you will focus on the process of creating the MessageService. Only after instantiation and initialization are completed will you return to the TaskService and inject the MessageService into the MessageService.
Why @Lazy can solve this problem
@Lazy is understood by most people as: this class will be loaded only when used.
This is what spring hopes we see, but this description is actually not completely accurate. For example:
@Service public class TaskService { @Resource @Lazy private MessageService messageService; public void shit(){ (); } }
- Here, @Lazy is added to the messageService property. When instantiating TaskService and populateBean, in the getResourceToInject method of CommonAnnotationBeanPostProcessor, spring finds that the messageService is modified by @Lazy annotation, and it will be wrapped into a proxy object: that is, create a TargetSource, override the getTarget method, and returns the getResource(beanName) method in CommonAnnotationBeanPostProcessor (the logic in the method body can be understood as getting the object from the three-layer cache of the factory). In other words, the agent object of the TaskService is injected into a MessageService (this is the third agent scenario that appears in this article).
- When spring instantiates the MessageService, it does not care whether it is modified by @Lazy, but it will only be created as an ordinary bean. After success, it will be placed in the first-level cache (so strictly speaking, it cannot be said that it is "loaded after use").
- After the container is started, when TaskService needs to use the messageService method, it will execute the logic of the proxy object, obtain the TargetSource, and call getResource to get the real object of the messageService from the third-layer cache. Since the messageService has been completely created by spring at this time, it is in the first-level cache singletonObjects, so you can use it with confidence after getting it.
This is the article about the reasons and solutions for Spring's circular dependencies using @Async. For more related Spring @Async circular dependencies, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!