Async-метод помечен аннотацией, тесты в IDE отрабатывают без ошибок — и это главная ловушка: локально всё выглядит рабочим, потому что метод просто выполняется, только синхронно.
Spring оборачивает бин прокси (CGLIB или JDK dynamic proxy, зависит от настроек). Аннотация перехватывается только когда вызов идёт через этот прокси — то есть снаружи, через ссылку на бин из контекста.
Когда метод вызывается изнутри того же класса через this, Java делает обычный прямой вызов. Прокси в цепочке вообще не участвует, и всё, что на него завешано — @Async, @Transactional, @Cacheable, @Retryable — просто не срабатывает.
@Service public class NotificationService {
public void handleEvent(Event event) { sendAsync(event); }
@Async public void sendAsync(Event event) { emailClient.send(event); } }
Вызов sendAsync(event) внутри handleEvent выполнится в том же потоке, синхронно. Внешний поток, вызвавший handleEvent, дождётся отправки письма целиком, хотя по коду ожидалось, что он пойдёт дальше сразу.
Дальше спросят: как починить без выноса метода в отдельный бин. Ответ — self-injection: внедрить в класс ApplicationContext или саму себя через @Lazy ссылку на бин, и вызывать sendAsync через неё, а не через this. Это заставляет вызов пройти через прокси.
Аннотация не защищает от того, как именно вызван метод — только от того, через какой объект.
senior·base — что спрашивают на самом деле
В этом посте были ссылки, но мы их удалили по правилам Сетки