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
@Async
annotation work? - Why is Spring unable to resolve circular dependencies when the
@Async
annotation 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.
-
AnnotationAwareAspectJAutoProxyCreator
is executed first andAsyncAnnotationBeanPostProcessor
is executed later, becauseAnnotationAwareAspectJAutoProxyCreator
is in front. -
The results of the
AnnotationAwareAspectJAutoProxyCreator
processing are passed as input parameters to theAsyncAnnotationBeanPostProcessor
, which is how theapplyBeanPostProcessorsAfterInitialization
method 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
@Async
annotation, you can achieve their own asynchronous through the thread pool, so there is no@Async
annotation, 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
@Lazy
annotation on the fields of cyclic dependency injection. -
From the source code comment above, we can see that when
allowRawInjectionDespiteWrapping
istrue
, it won’t take thatelse if
and won’t throw an exception, so we can solve the error problem by settingallowRawInjectionDespiteWrapping
totrue
, 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