spring-projects / spring-projects/spring-framework

Problem in `@Async` annotation and circular dependencies

Open
#35,554 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

in: core status: waiting-for-triage
Dominant language
Java
Stars
60.2k
Forks
38.8k
Avg merge
5d 2h
Merged PRs (30d)
27

Description

Hi developers,

When class A and class B have a circular dependency, and A is annotated with @Async (meaning A will be processed by AsyncAnnotationBeanPostProcessor and wrapped in a proxy)

@Service
public class A {
    @Autowired
    private B b;
    @Async
    public void test () {
        System.out.println("async");
    }
}
@Service
@DependsOn("a")
public class B {
    @Autowired
    private A a;

}

then the following exception occurs:

Error creating bean with name 'a': Bean with name 'a' has been injected into other beans [b] in its raw version as part of a circular reference, ......

From the source code of doCreateBean, I found that this happens because exposedObject != bean.

Image

Here above, exposedObject refers to the proxy object, while bean refers to the original object. The reason exposedObject becomes the proxy object is that even though AsyncAnnotationBeanPostProcessor.postProcessAfterInitialization checks whether the bean has already been proxied(the following picture), the proxy object itself stays in the cache, so neither exposedObject nor bean point to it. As a result, exposedObject is not an implementation of Advised and finally returns a new proxy object, which makes exposedObject != bean, and thus the exception is thrown.

Image

In older versions of Spring, I found that the root cause was that AsyncAnnotationBeanPostProcessor did not implement SmartInstantiationAwareBeanPostProcessor. Therefore, AsyncAnnotationBeanPostProcessor did not participate in getEarlyBeanReference, which later caused postProcessAfterInitialization to return a new proxy object.

In the current versions, AsyncAnnotationBeanPostProcessor has already been changed to implement SmartInstantiationAwareBeanPostProcessor. However, it does not override the getEarlyBeanReference method, which means the issue still exists.

So my question is:
Why doesn’t AsyncAnnotationBeanPostProcessor override getEarlyBeanReference to wrap it instead of returning the bean itself

This answer was translated with AI, so there might be some inaccuracies. Thank you for your clarification!!!!

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the circular A/B dependency from the issue, then trace bean creation through doCreateBean and AsyncAnnotationBeanPostProcessor. Read SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference alongside postProcessAfterInitialization to compare the early and final references. Done means determining whether the exception is expected and documenting or testing the supported behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.