При использовании @Async Spring создаёт новый поток из пула для выполнения метода. Если в основном потоке вы храните данные в ThreadLocal (например, контекст пользователя, tenant ID или trace-информацию) — в асинхронном методе они будут недоступны.
Классическая ловушка:
// Основной поток — кладём данные
UserContext.set(currentUser);
// Вызов @Async метода — данные уже не там
asyncService.processOrder(orderId);
🔹 Решение
▪️ Создать кастомный Executor с передачей контекста через TaskDecorator:
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(runnable -> {
User user = UserContext.get(); // захватываем из текущего потока
return () -> {
try {
UserContext.set(user); // передаём в новый поток
runnable.run();
} finally {
UserContext.clear(); // обязательно чистим
}
};
});
executor.setCorePoolSize(4);
executor.setMaxPoolSize(10);
executor.initialize();
return executor;
}
▪️ Использовать @Async("asyncExecutor") с явным указанием бина:
@Async("asyncExecutor")
public CompletableFuture<Void> processOrder(Long orderId) {
// UserContext.get() теперь вернёт правильного пользователя
}Без finally { UserContext.clear() } — утечка контекста гарантирована: потоки из пула переиспользуются, и следующий запрос получит чужие данные.
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#Enterprise