Добрый день!
Согласно документации для асинхронный запросов необходимо чтобы метод возвращал объект типа DeferredResult.
Порядок обработки методов, возвращающих DeferredResult:
- Контроллер возвращает DeferredResult и сохраняет его в некоторой очереди или списке в памяти, где к нему можно получить доступ.
- Spring MVC вызывает request.startAsync().
- При этом DispatcherServlet и все настроенные фильтры выходят из потока обработки запроса, но ответ (соединение с клиентом) остается открытым.
- Приложение устанавливает значение DeferredResult из некоторого потока, а Spring MVC отправляет запрос обратно в контейнер сервлетов.
- Вызывается снова DispatcherServlet и обработка возобновляется с асинхронно полученным возвращаемым значением.
Как итог, получается есть два вида потоков: одни обрабатывают запросы, передают их другим потокам, сами освобождаются и готовы принимать новые запросы. Когда вторые потоки выполнят свою работу, они возвращают ее результат первым и те возвращают результат в качестве ответа клиенту.
Попробовал реализовать это на Tomcat и получилось иначе.
1. Устанавливаю в Tomcat максимальное потоков, которые обрабатывают запросы, равное 3.
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxParameterCount="1000"
URIEncoding="UTF-8"
maxThreads="3"
/>
2. Настраиваю Spring MVC.
Т.к. использую AbstractAnnotationConfigDispatcherServletInitializer, то поддержка асинхронности подключена по умолчанию.
Так же в файле конфигурации прописываю
@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.setCorePoolSize(10);
threadPoolTaskExecutor.setThreadNamePrefix("mvc-task-");
return threadPoolTaskExecutor;
}
}
3. Создаю контроллер
Для каждого метода делаем задержку потока в 30 секунд, чтобы проще проследить.
@Controller
@RequestMapping("/")
public class MainController {
@GetMapping("/defer")
DeferredResult<String> getDefer(HttpServletRequest request)
{
try
{
Thread.sleep(30000);
}
catch(InterruptedException ex)
{
System.out.println("MainController getDefer InterruptedException");
Thread.currentThread().interrupt();
}
DeferredResult<String> result = new DeferredResult<>();
result.setResult("defer");
return result;
}
@GetMapping("/defer2")
DeferredResult<String> getDefer2(HttpServletRequest request)
{
try
{
Thread.sleep(30000);
}
catch(InterruptedException ex)
{
System.out.println("MainController getDefer2 InterruptedException");
Thread.currentThread().interrupt();
}
DeferredResult<String> result = new DeferredResult<>();
result.setResult("defer2");
return result;
}
@GetMapping("/defer3")
DeferredResult<String> getDefer3(HttpServletRequest request)
{
try
{
Thread.sleep(30000);
}
catch(InterruptedException ex)
{
System.out.println("MainController getDefer3 InterruptedException");
Thread.currentThread().interrupt();
}
DeferredResult<String> result = new DeferredResult<>();
result.setResult("defer3");
return result;
}
@GetMapping("/async")
String getAsync(HttpServletRequest request)
{
startAsync();
return "async";
}
@Async
void startAsync()
{
try
{
Thread.sleep(30000);
}
catch(InterruptedException ex)
{
System.out.println("MainController getAsync InterruptedException");
Thread.currentThread().interrupt();
}
}
@GetMapping("/async2")
String getAsync2(HttpServletRequest request)
{
startAsync();
return "async2";
}
}
4. Отслеживать будем в менеджере Tomcat по адресу
localhost:8080/manager/status
Помним, что всего потока будет 3. Так что когда будем запускать менеджер, один поток будет уходить на его обработку.
4.1 Обращаемся по адресам /defer и /defer2
Видим, что потоки не были высвобождены, и, следовательно, если загрузить /defer3, то я уже не сразу смогу открыть менеджер Tomcat, т.к. все три потока будут заняты и будут обрабатывать запросы по адресам /defer, /defer2, /defer3 (каждый поток мы удерживаем по 30 секунд).
4.2 Обращаемся по адресам /defer и /async
Здесь мы видим, что и метод с @Async занимает поток, который должен обрабатывать входящие запроса, метод же с @Async должен работать вне контекста потоков.
ВОПРОС: ТАК КАК РАБОТАЕТ АСИНХРОННОСТЬ В SPRING MVC?