SimpleAsyncTaskExecutor拦截器:启用请求作用域并传递请求Bean
Hey Brian, great question—let’s break this down step by step since you need to pass request-scoped beans to async tasks using SimpleAsyncTaskExecutor while keeping its "new thread per task" behavior (which is exactly its default behavior, so we don’t have to mess with that!).
SimpleAsyncTaskExecutor The key here is to wrap the async task with a decorator that captures the current request context from the parent thread, then injects it into the new thread created by SimpleAsyncTaskExecutor. This lets Spring resolve your request-scoped bean correctly in the async context.
Step 1: Create a Request Context Task Decorator
First, make a decorator that saves the request attributes before the async task runs, then sets them in the new thread (and cleans up afterward to avoid memory leaks):
import org.springframework.core.task.TaskDecorator; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; public class RequestContextTaskDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { // Capture the current request context from the calling thread RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); return () -> { try { // Set the captured context in the async thread RequestContextHolder.setRequestAttributes(requestAttributes); // Run the original async task runnable.run(); } finally { // Clean up the context to prevent memory leaks RequestContextHolder.resetRequestAttributes(); } }; } }
Step 2: Configure a Custom SimpleAsyncTaskExecutor
Next, create a bean for your custom executor that uses the decorator. This keeps SimpleAsyncTaskExecutor's "new thread per task" behavior intact:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.concurrent.SimpleAsyncTaskExecutor; @Configuration public class AsyncExecutorConfig { @Bean(name = "requestScopedAsyncExecutor") public TaskExecutor requestScopedAsyncExecutor() { SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(); // Attach our request context decorator executor.setTaskDecorator(new RequestContextTaskDecorator()); // Optional: Set a thread name prefix for easier debugging executor.setThreadNamePrefix("request-scoped-async-"); return executor; } }
Step 3: Use the Executor in Your @Async Methods
Now just specify your custom executor in the @Async annotation, and your request-scoped bean will work as expected in the async thread:
import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service public class MyAsyncService { // Inject your request-scoped bean directly private final MyRequestScopedBean requestScopedBean; public MyAsyncService(MyRequestScopedBean requestScopedBean) { this.requestScopedBean = requestScopedBean; } @Async("requestScopedAsyncExecutor") public void runAsyncTask() { // You can now use the request-scoped bean normally here String requestSpecificValue = requestScopedBean.getSomeValue(); // ... your async business logic ... } }
Bonus: Global Async Configuration
If you want all @Async methods to use this executor by default, implement AsyncConfigurer instead:
import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.SimpleAsyncTaskExecutor; import java.util.concurrent.Executor; @Configuration @EnableAsync public class GlobalAsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(); executor.setTaskDecorator(new RequestContextTaskDecorator()); executor.setThreadNamePrefix("global-request-scoped-async-"); return executor; } }
Why This Works
SimpleAsyncTaskExecutorcreates a new thread for every task by default—perfect for your requirement of no thread reuse.- The decorator copies the request context from the original request thread to the new async thread, so Spring can resolve your request-scoped bean just like it would in the main request thread.
- The
finallyblock ensures we clean up the context after the task finishes, preventing memory leaks.
内容的提问来源于stack exchange,提问作者Brian

