Как реализуется асинхронность в Spring MVC?

Добрый день! <br/> <br/> В соответствии с документацией, для реализации асинхронных запросов метод должен возвращать объект типа DeferredResult. <br/> <br/> Порядок обработки методов, которые возвращают DeferredResult, выглядит следующим образом: <br/> <br/> В итоге, можно выделить два типа потоков: одни потоки обрабатывают входящие запросы, передают их на обработку другим потокам, освобождаются и подготавливаются к принятию новых запросов. Когда вторые потоки завершают свою работу, они возвращают результаты первым потокам, которые, в свою очередь, предоставляют результат в ответ клиенту. <br/> <br/> <b>Я попытался реализовать эту логику на сервере Tomcat, но столкнулся с другой ситуацией.</b> <br/> <br/> 1. Я устанавливаю в Tomcat максимальное количество потоков, обрабатывающих запросы, равное 3. <br/> <br/> <pre><code class="xml">&lt;Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"
               maxParameterCount="1000"
               URIEncoding="UTF-8"
               maxThreads="3" 
               /&gt;</code></pre> <br/> <br/> 2. Далее настраиваю Spring MVC. <br/> <br/> Поскольку я использую AbstractAnnotationConfigDispatcherServletInitializer, поддержка асинхронных операций активирована по умолчанию. <br/> <br/> В конфигурационном файле также указываю следующее: <br/> <br/> <pre><code class="java">@Configuration
@EnableWebMvc
@ComponentScan
@EnableAsync
public class MainWebConfig implements WebMvcConfigurer {
	
	 @Override 
	 public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
		 configurer.setTaskExecutor(mvcTaskExecutor()); 
		 configurer.setDefaultTimeout(30_000); 
	 } 
	 
	 @Bean 
	 public ThreadPoolTaskExecutor mvcTaskExecutor() { 
		 ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
		 threadPoolTaskExecutor.se
  • 11 апреля 2025 г. 16:25
Ответы на вопрос 2
Ваше описание реализации асинхронности в Spring MVC достаточно ясное, и вы правильно понимаете основные принципы, связанные с использованием `DeferredResult`. Давайте разберем ваши шаги и уточним, как настраивается асинхронная обработка в Spring MVC.

### 1. Настройка Tomcat

Вы установили максимальное количество потоков, обрабатывающих запросы, равным 3. Это нормально, но помните, что если у вас есть более 3 асинхронных запроса, они будут ожидать, пока один из потоков освободится.

### 2. Настройка Spring MVC

Вы правильно включили поддержку асинхронных операций с использованием аннотации `@EnableAsync` и реализовали интерфейс `WebMvcConfigurer` для настройки асинхронной поддержки.

Вот дополнение к вашему коду, чтобы фрагмент с настройкой `ThreadPoolTaskExecutor` был завершен:

```java
@Bean
public ThreadPoolTaskExecutor mvcTaskExecutor() {
    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    threadPoolTaskExecutor.setCorePoolSize(3); // Минимальное количество потоков
    threadPoolTaskExecutor.setMaxPoolSize(10); // Максимальное количество потоков
    threadPoolTaskExecutor.setQueueCapacity(50); // Вместимость очереди
    threadPoolTaskExecutor.initialize(); 
    return threadPoolTaskExecutor;
}
```

### 3. Использование DeferredResult

Теперь, когда вы настроили асинхронную обработку, вы можете создавать контроллеры, которые возвращают `DeferredResult`. Пример:

```java
@RestController
@RequestMapping("/api")
public class MyController {

    @GetMapping("/async")
    public DeferredResult<String> handleAsyncRequest() {
        DeferredResult<String> deferredResult = new DeferredResult<>(30000L); // Тайм-аут 30 секунд

        // Симуляция асинхронной работы
        myAsyncService.performAsyncTask(deferredResult);
        
        return deferredResult;
    }
}
```

### 4. Асинхронная логика

Ваш сервис должен принимать `DeferredResult` в качестве аргумента и выполнять асинхронную работу. Пример:

```java
@Service
public class MyAsyncService {

    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;

    public void performAsyncTask(DeferredResult<String> deferredResult) {
        taskExecutor.execute(() -> {
            try {
                // Эмуляция длительной операции
                Thread.sleep(5000); // 5 секунд
                deferredResult.setResult("Результат обработки");
            } catch (InterruptedException e) {
                deferredResult.setErrorResult("Произошла ошибка");
            }
        });
    }
}
```

### Параметры производительности

Обратите внимание на параметры `CorePoolSize`, `MaxPoolSize` и `QueueCapacity`. Вам нужно их настраивать в зависимости от потребностей вашего приложения. Если все потоки заняты, новые запросы будут ставиться в очередь (до достижения `QueueCapacity`), после чего будет возникать `RejectedExecutionException`.

### Заключение

Таким образом, вы настроили асинхронный контроллер, который использует `DeferredResult`. Если вы сталкиваетесь с проблемами или неполадками, убедитесь, что потоки правильно освобождаются, и следите за использованием ресурсов, особенно в среде с ограниченными потоками, как в вашем случае.
Добрый вечер! 
В контроллерах getDefer1, 2, 3 вы блокируете поток томкета, правильнее будет переделать на какой-нибудь вариант типо такого:
@GetMapping("/defer")	
DeferredResult<String> getDefer() {
    DeferredResult<String> result = new DeferredResult<>();
    CompletableFuture.runAsync(() -> {
        try {
            Thread.sleep(30000);
            result.setResult("defer");
        } catch (InterruptedException e) {
            result.setErrorResult("Timeout");
        }
    }, mvcTaskExecutor());
    return result;
}

Так должно работать так как вы хотите. С async не работает, потому что спринг не умеет перехватывать вызовы @Async внутри одного класса, нужно чтобы обязательно был другой бин
Похожие вопросы