Как использовать сервлет 3.1 в весенний MVC?
Доступны 2 разные функции:
-
servlet 3.0 позволяет обрабатывать запрос в потоке, отличном от потока контейнера.
-
Сервлет 3.1 позволяет читать/записывать в сокет, не блокируя поток чтения/записи
В интернете много примеров использования сервлета 3.0. Мы можем использовать его spring очень легко. Нам просто нужно вернуть DefferedResult
или CompletableFuture
Но я не могу найти пример использования сервлета 3.1 spring. Насколько я знаю, мы должны зарегистрировать WriteListener
и ReadListener
и выполнить грязную работу внутри. Но я не могу найти пример этого слушателя. Я считаю, что это не очень легко.
Не могли бы вы привести пример функции сервлета 3.1 spring с объяснением реализации Listener?
Ответы
Ответ 1
Если вы ищете пример неблокирующего объявления API-интерфейса Spring/Servlet 3.1, попробуйте следующее:
@GetMapping(value = "/asyncNonBlockingRequestProcessing")
public CompletableFuture<String> asyncNonBlockingRequestProcessing(){
ListenableFuture<String> listenableFuture = getRequest.execute(new AsyncCompletionHandler<String>() {
@Override
public String onCompleted(Response response) throws Exception {
logger.debug("Async Non Blocking Request processing completed");
return "Async Non blocking...";
}
});
return listenableFuture.toCompletableFuture();
}
Требуется Spring Web 5. 0+ и поддержка Servlet 3.1 на уровне контейнера сервлетов (Tomcat 8. 5+, Jetty 9. 4+, WildFly 1 0+)
Ответ 2
Не должно быть слишком сложно преследовать некоторые примеры. Я нашел один из IBM в WASdev/sample.javaee7.servlet.nonblocking. Для работы с API javax.servlet
в Spring или Spring Boot достаточно просто попросить Spring ввести HttpServletRequest
или HttpServletResponse
. Итак, простой пример может быть:
@SpringBootApplication
@Controller
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@RequestMapping(path = "")
public void writeStream(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletOutputStream output = response.getOutputStream();
AsyncContext context = request.startAsync();
output.setWriteListener(new WriteListener() {
@Override
public void onWritePossible() throws IOException {
if ( output.isReady() ) {
output.println("WriteListener:onWritePossible() called to send response data on thread : " + Thread.currentThread().getName());
}
context.complete();
}
@Override
public void onError(Throwable t) {
context.complete();
}
});
}
}
Это просто создает WriteListener
и присоединяет его к потоку вывода запроса, а затем возвращает. Ничего фантастического.
РЕДАКТИРОВАТЬ: Дело в том, что контейнер сервлета, например, Tomcat, вызывает onWritePossible
когда данные могут быть записаны без блокировки. Подробнее об этом в разделе "Неблокирующие операции ввода-вывода с использованием Servlet 3.1: масштабируемые приложения с использованием Java EE 7" (TOTD # 188). ,
У слушателей (и авторов) есть методы обратного вызова, которые вызываются, когда контент доступен для чтения или может быть записан без блокировки.
Поэтому onWritePossible
вызывается только тогда, когда out.println
может быть вызван без блокировки.
Вызов методов setXXXListener указывает, что вместо традиционного ввода-вывода используется неблокирующий ввод-вывод.
Предположительно, что вы должны сделать, проверьте output.isReady
чтобы узнать, можете ли вы продолжать записывать байты. Похоже, что вам придется иметь какое-то неявное соглашение с отправителем/получателем о размерах блоков. Я никогда не использовал его, так что я не знаю, но вы попросили пример этого в среде Spring и то, что предоставляется.
Поэтому onWritePossible вызывается только тогда, когда out.println может быть вызван без блокировки. Звучит правильно, но как понять, сколько байтов можно записать? Как мне это контролировать?
РЕДАКТИРОВАТЬ 2: Это очень хороший вопрос, на который я не могу дать вам точный ответ. Я предполагаю, что onWritePossible
вызывается, когда сервер выполняет код в отдельном (асинхронном) потоке от основного сервлета. В этом примере вы проверяете input.isReady()
или output.isReady()
и я предполагаю, что он блокирует ваш поток до тех пор, пока отправитель/получатель не будет готов к дальнейшим действиям. Поскольку это делается асинхронно, сам сервер не блокируется и может обрабатывать другие запросы. Я никогда не использовал это, поэтому я не эксперт.
Когда я сказал, что будет какое-то неявное соглашение с отправителем/получателем о размерах блоков, это означает, что, если получатель способен принимать блоки по 1024 байта, вы должны записать это количество, когда output.isReady
имеет значение true. Вы должны были бы знать об этом, читая документацию, ничего в API об этом. В противном случае вы можете написать отдельные байты, но в примере из оракула используются блоки по 1024 байта. 1024-байтовые блоки - это достаточно стандартный размер блока для потокового ввода-вывода. В приведенном выше примере должен быть расширен, чтобы писать байты в while
циклы, как это показано на примере Oracle. Это упражнение осталось для читателя.
Проектный реактор и Spring Webflux имеют концепцию backpressure
которая может решить эту проблему более тщательно. Это был бы отдельный вопрос, и я не изучал, как это объединяет отправителей и получателей (или наоборот).
Ответ 3
Я нашел эту статью на dZone. Надеюсь это поможет.
https://dzone.com/articles/servlet-31spring-mvc-non-blocking-io
Ответ 4
Для сервлета 3.1 вы можете поддерживать неблокирующий ввод/вывод, используя мост Reactive Streams
Сервлет 3. 1+ Контейнер
Чтобы развернуть как WAR файл в любой контейнер сервлета 3. 1+, вы можете расширить и включить {api-spring-framework}/web/server/adapter/AbstractReactiveWebInitializer.html [AbstractReactiveWebInitializer] в WAR. Этот класс оборачивает HttpHandler с ServletHttpHandlerAdapter и регистрирует его как сервлет.
Поэтому вы должны расширить AbstractReactiveWebInitializer, который добавляет поддержку асинхронности
registration.setAsyncSupported(true);
И поддержка в ServletHandpHandlerAdapter
AsyncContext asyncContext = request.startAsync();