Background
Today I found that a colleague’s spring boot project failed to start, and the exception log output from the console is as follows.
|
|
When I saw the BeanCurrentlyInCreationException exception, my first thought was that there was a cyclic dependency problem. But on second thought, hasn’t Spring already solved the cyclic dependency problem? How can this exception still be thrown.
Upon closer inquiry, I learned that he annotated the @Async annotation on the method of the circular dependency bean.
Here I simulate the code at that time, AService and BService refer to each other, and the save() method of AService is annotated with @Async.
That is, this code causes a BeanCurrentlyInCreationException exception to be thrown when the application starts. Could it be that Spring is unable to solve the circular dependency problem when the @Async annotation meets it? To verify this suspicion, I removed the @Async annotation and started the project again and it worked. So basically we can conclude that Spring can’t solve the cyclic dependency problem when the @Async annotation meets the cyclic dependency.
Although the cause of the problem has been found, it leads to the following questions.
- How does the
@Asyncannotation work? - Why is Spring unable to resolve circular dependencies when the
@Asyncannotation meets a circular dependency? - How do I resolve a cyclic dependency exception after it occurs?
How does the @Async annotation work?
The @Async annotation works with the AsyncAnnotationBeanPostProcessor class, which handles the @Async annotation. The objects of the AsyncAnnotationBeanPostProcessor class are put into the Spring container by the @EnableAsync annotation, which is the fundamental reason why the @EnableAsync annotation is needed to activate the @Async annotation to work.

AsyncAnnotationBeanPostProcessor class system
This class implements the BeanPostProcessor interface and implements the postProcessAfterInitialization method, which is implemented in its parent class AbstractAdvisingBeanPostProcessor, which means that when the initialization phase of the bean is completed will call back the postProcessAfterInitialization method of the AsyncAnnotationBeanPostProcessor. The reason for the callback is that all the postProcessAfterInitialization methods of the BeanPostProcessor will be called back after the initialization of the bean is completed in the bean’s lifecycle, with the following code.
|
|
The implementation of AsyncAnnotationBeanPostProcessor for the postProcessAfterInitialization method is as follows.
|
|
The main role of this method is used for the method input parameter object dynamic proxy, when the input parameter object class annotated @Async annotation, then the method will generate a dynamic proxy object for this object, and finally will return the input parameter object proxy object. As for how to determine whether the method is annotated with @Async annotation, it relies on isEligible(bean, beanName) to determine. Since this code involves the knowledge of the underlying dynamic proxy, it will not be expanded in detail here.

AsyncAnnotationBeanPostProcessor role
In summary, we can conclude that when the initialization phase of the bean creation process is completed, the postProcessAfterInitialization method of the AsyncAnnotationBeanPostProcessor is called, and the object of the class annotated with the @Async annotation is dynamically proxied. and then returns a proxy object back.
Although we conclude that the role of the @Async annotation relies on dynamic proxying, but here actually raises another question, that is, the transaction annotation @Transactional or the custom AOP facet, they are also implemented through dynamic proxying, why when using these, no circular dependency exception is thrown? Is their implementation different from that of the @Async annotation? Well, it’s not really the same, so read on.
How is AOP implemented?
We all know that AOP relies on dynamic proxies and works in the bean’s lifecycle, specifically by AnnotationAwareAspectJAutoProxyCreator class, which goes through the bean’s lifecycle to handle the tangents, transaction annotations, and then generates dynamic proxies. The objects of this class are automatically injected into the Spring container when the container is started.
AnnotationAwareAspectJAutoProxyCreator also implements BeanPostProcessor and also implements postProcessAfterInitialization method.
|
|
The wrapIfNecessary method will then dynamically proxy the bean, if your bean needs to be dynamically proxied.
AnnotationAwareAspectJAutoProxyCreator role
That is, although both AOP and @Async annotations are dynamic proxies at the bottom, the specific classes that implement them are different. The general AOP or transaction dynamic proxy relies on the AnnotationAwareAspectJAutoProxyCreator implementation, while the @Async relies on the AsyncAnnotationBeanPostProcessor implementation, and both work after the initialization is completed, which is also This is the main difference between the @Async annotation and AOP, that is, the classes handled are different.
How Spring resolves cyclic dependencies
Spring relies on the three-level cache to solve the circular dependency.
Simply put, by caching the object being created corresponding to the ObjectFactory, you can get the object being created by the early reference to the object, when there is a circular dependency, because the object is not created, you can just inject by getting the early reference to the object.
The ObjectFactory code is as follows.
|
|
As you can see, the cached ObjectFactory is actually a lamda expression, and the real way to get the earlier exposed reference objects is actually through the getEarlyBeanReference method.
getEarlyBeanReference
|
|
The getEarlyBeanReference implementation calls all the getEarlyBeanReference methods of the SmartInstantiationAwareBeanPostProcessor.
The AnnotationAwareAspectJAutoProxyCreator class mentioned earlier implements the SmartInstantiationAwareBeanPostProcessor interface, and the getEarlyBeanReference method is implemented in the parent class in the parent class.
|
|
This method will finally call the wrapIfNecessary method, which, as mentioned earlier, is a way to get a dynamic proxy that will be proxied if needed, such as a transaction annotation or a custom AOP that will be done when exposed early on.
This finally made it clear that what was exposed early on was probably a proxy object, and was eventually obtained through the getEarlyBeanReference method of the AnnotationAwareAspectJAutoProxyCreator class.
But AsyncAnnotationBeanPostProcessor does not implement SmartInstantiationAwareBeanPostProcessor, that is, it does not call AsyncAnnotationBeanPostProcessor to process the @Async annotation at the stage of getting the early object.
Why can’t Spring solve the problem of circular dependency when the @Async annotation is annotated?
Let’s take the previous example here, AService adds @Async annotation, AService is created first, BService is found to be referenced, then BService goes to create it, and when Service is created, AService is found to be referenced, then it will be created by AnnotationAwareAspectJAutoProxyCreator class implements the getEarlyBeanReference method to get the early reference object of AService, at this time this early reference object may be proxied, depending on whether AService needs to be proxied, but It must not be a proxy that handles the @Async annotation, for the reasons mentioned above.
So after BService is created and injected into AService, then AService will continue to process, and as said before, when the initialization phase is completed, all the BeanPostProcessor implementations will call the postProcessAfterInitialization method. So it will call back the postProcessAfterInitialization method implementation of AnnotationAwareAspectJAutoProxyCreator and AsyncAnnotationBeanPostProcessor in turn.
This callback has two details.
-
AnnotationAwareAspectJAutoProxyCreatoris executed first andAsyncAnnotationBeanPostProcessoris executed later, becauseAnnotationAwareAspectJAutoProxyCreatoris in front.
-
The results of the
AnnotationAwareAspectJAutoProxyCreatorprocessing are passed as input parameters to theAsyncAnnotationBeanPostProcessor, which is how theapplyBeanPostProcessorsAfterInitializationmethod is implemented
AnnotationAwareAspectJAutoProxyCreator callback: it will find that the AService object has been referenced earlier, nothing is processed, and the AService object is returned directly .
AsyncAnnotationBeanPostProcessor callback: it finds that AService annotated with the @Async annotation, then it creates a dynamic proxy for the object returned by AnnotationAwareAspectJAutoProxyCreator and returns it.
After this callback, have you found the problem. The object exposed earlier may be AService itself or a proxy object of AService, and it is implemented through the AnnotationAwareAspectJAutoProxyCreator object. But with the AsyncAnnotationBeanPostProcessor callback, a proxy object for the AService object is created, which results in the object exposed earlier by AService not being the same as the object created completely in the end, so it’s definitely not right. How can the same bean exist in a Spring with two different objects, so it will throw BeanCurrentlyInCreationException exception, the code of this judgment logic is as follows.
|
|
So, the reason why the @Async annotation encounters a circular dependency that Spring cannot resolve is because the @Aysnc annotation makes the bean that is eventually created not the same object as the bean that was exposed earlier, so it throws an exception.
Spring can’t solve this problem because the @Aysnc annotation makes the final bean created not the same object as the earlier exposed bean, so it throws an exception.
How do I resolve a circular dependency exception after it occurs?
There are many ways to solve this problem
-
adjust the dependency relationship between objects, fundamentally eliminate the circular dependency, there is no circular dependency, there is no such thing as early exposure, then there will be no problem.
-
do not use the
@Asyncannotation, you can achieve their own asynchronous through the thread pool, so there is no@Asyncannotation, you will not generate a proxy object at the end, resulting in early exposure out of the object is not the same. -
You can annotate the
@Lazyannotation on the fields of cyclic dependency injection. -
From the source code comment above, we can see that when
allowRawInjectionDespiteWrappingistrue, it won’t take thatelse ifand won’t throw an exception, so we can solve the error problem by settingallowRawInjectionDespiteWrappingtotrue, the code is as follows.1 2 3 4 5 6 7 8 9@Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { ((DefaultListableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true); } }Although this setup solves the problem, it is not recommended because it allows the early injected objects to be different from the final created objects and may result in the final generated objects not being dynamically proxied.
Reference https://www.lifengdi.com/archives/transport/technology/3953