Unlocking Spring’s @Async: Deep Dive into Mechanics, Pitfalls, and Solutions
This article explores Spring’s @Async annotation in depth, detailing its basic usage, underlying proxy creation, pointcut logic, and asynchronous execution, while highlighting common issues such as circular dependencies and inefficient default thread pools, and providing practical solutions like lazy injection and custom executors.
Preface
Recently I have been researching transaction‑related topics. I wrote this article because a previous post about circular dependencies sparked many questions when readers added Spring's @Async annotation, which broke the circular‑dependency resolution.
Article Highlights
@Async Basic Usage
The annotation makes the annotated method execute asynchronously, but two prerequisites must be met: 1. Add @EnableAsync on a configuration class. 2. The class containing the method must be managed by Spring. 3. Annotate the method with @Async .
Let's see a demo:
Step 1: Enable async in a configuration class
@EnableAsync
@Configuration
@ComponentScan("com.dmz.spring.async")
public class Config {
}Step 2: Create a service with @Async
@Component
public class DmzAsyncService {
@Async // asynchronous execution
public void testAsync() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("testAsync invoked");
}
}Step 3: Test the async execution
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
DmzAsyncService bean = ac.getBean(DmzAsyncService.class);
bean.testAsync();
System.out.println("main function finished");
}
}
// Output:
// main function finished
// testAsync invokedFrom the example we see that testAsync runs asynchronously. How does Spring achieve this?
Principle Analysis
Where the proxy is created
The key is AsyncAnnotationBeanPostProcessor, which creates the proxy in its postProcessAfterInitialization method (inherited from AbstractAdvisingBeanPostProcessor).
Pointcut logic
The pointcut matches either a class annotated with @Async or a method annotated with @Async, using two AnnotationMatchingPointcut instances.
Advice logic
The advisor builds an AnnotationAsyncExecutionInterceptor. Its invoke method obtains an AsyncTaskExecutor, wraps the method call in a Callable, and submits it to the executor.
Executor determination
If no executor is specified on the annotation, Spring falls back to a default executor obtained from AsyncExecutionAspectSupport. The default is SimpleAsyncTaskExecutor, which creates a new thread for each task, has no thread‑pool limits, and does not reuse threads.
Problems and Solutions
Problem 1: Circular‑dependency error
When @Async creates a proxy, the early reference exposed during bean creation is the original object, not the proxy, causing a circular‑dependency failure. The fix is to inject the dependency lazily:
@Component
public class B implements BService {
@Autowired
@Lazy
private A a;
public void doSomething() {}
}The @Lazy annotation forces Spring to inject a proxy that resolves the actual bean only when first used, breaking the circular reference.
Problem 2: Default thread pool does not reuse threads
The default SimpleAsyncTaskExecutor creates a new thread per task, leading to unbounded thread creation and possible OOM. The recommended solution is to provide a custom thread pool.
Solution A: Implement AsyncConfigurer
public class DmzAsyncConfigurer implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
// create and return a custom ThreadPoolExecutor
}
}Solution B: Specify a qualified executor in @Async
@Async("dmzExecutor")
public void doSomething() {}
@Configuration
public class Config {
@Bean("dmzExecutor")
public Executor executor() {
// create and return a custom ThreadPoolExecutor
}
}Conclusion
This article introduced the usage, inner workings, and common pitfalls of Spring's @Async annotation, and offered concrete solutions such as lazy injection for circular dependencies and custom executors to replace the inefficient default thread pool.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
