Primavera MVC tiene una amplia integración del procesamiento de solicitudes asincrónicas en Servlet 3.0:
DeferredResult
yCallable
devuelven valores en los métodos del controlador proporciona soporte básico para un único valor de retorno asincrónico.Los controladores pueden transmitir múltiples valores, incluidos SSE y datos sin procesar.
Los controladores pueden usar clientes reactivos y devolver tipos reactivos para procesar respuestas.
DeferredResult
Una vez que se activa la función de procesamiento de solicitudes
asincrónicas habilitado en el contenedor de servlets, los métodos del controlador podrán encapsular cualquier valor
de retorno admitido del método del controlador usando DeferredResult
, como se muestra en el siguiente
ejemplo:
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
DeferredResult<String> deferredResult = new DeferredResult<String>();
// De algún otro hilo...
return deferredResult;
}
// Guardar resultado diferido en algún lugar...
deferredResult.setResult(result)
@GetMapping("/quotes")
@ResponseBody
fun quotes(): DeferredResult<String> {
val deferredResult = DeferredResult<String>()
// De algún otro hilo...
return deferredResult
}
// Guardar deferredResult en algún lugar...
deferredResult.setResult(result)
El controlador puede recibir el valor de retorno de forma asincrónica, desde otro hilo, por ejemplo, en respuesta a un evento externo (mensaje JMS), una tarea programada u otro evento.
Callable
El controlador puede ajustar cualquier valor de retorno admitido usando
java.util.concurrent.Callable
, como se muestra en el siguiente ejemplo:
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public String call() throws Exception {
// ...
return "someView";
}
};
}
@PostMapping
fun processUpload(file: MultipartFile) = Callable<String> {
// ...
"someView"
}
El valor de retorno se puede obtener ejecutando esta tarea a través del TaskExecutor
configurado
.
Procesamiento
El procesamiento asincrónico de solicitudes de servlet se describe brevemente a continuación:
ServletRequest
se puede configurar al modo asíncrono llamando arequest.startAsync()
. El efecto principal es que el servlet (así como cualquier filtro) puede completar la ejecución, pero la respuesta permanecerá abierta para que el procesamiento pueda finalizar más tarde.Llame a
request.startAsync()
devuelve unAsyncContext
que se puede utilizar para controlar aún más el procesamiento asincrónico. Por ejemplo, proporciona un métododispatch
, que es similar al envío desde la API de servlet, excepto que permite a la aplicación reanudar el procesamiento de la solicitud en el subproceso del contenedor de servlet.ServletRequest
proporciona acceso alDispatcherType
actual, que se puede utilizar para diferenciar entre procesamiento de solicitudes iniciales, envío asincrónico, reenvío y otros tipos de despachadores.
El procesamiento de DeferredResult
funciona de la siguiente manera:
El controlador devuelve
DeferredResult
y lo almacena en alguna cola o lista en la memoria donde se puede acceder.Spring MVC llama a
request.startAsync()
.Mientras tanto,
DispatcherServlet
y todos los filtros configurados salen del hilo de procesamiento de solicitudes, pero la respuesta permanece abierta.La aplicación establece
DeferredResult
desde algún subproceso y Spring MVC envía la solicitud de regreso al contenedor de servlets.DispatcherServlet
se llama nuevamente y el procesamiento se reanuda utilizando el valor de retorno recibido asincrónicamente.
El procesamiento de Callable
ocurre de la siguiente manera:
El controlador devuelve
Callable
.Spring MVC llama a
request.startAsync()
y pasaCallable
aTaskExecutor
para ser procesado en un hilo separado.Mientras tanto,
DispatcherServlet
y todos los filtros salen del subproceso del contenedor de servlets, pero la respuesta permanece abierta.Eventualmente,
Callable
produce un resultado y Spring MVC envía la solicitud de regreso a el contenedor de servlets para completar el procesamiento.DispatcherServlet
se llama nuevamente y el procesamiento se reanuda utilizando el valor de retorno recibido asincrónicamente deCallable
.
Para recibir Para obtener más información y contexto, consulte también publicaciones de blog que introdujeron soporte para el manejo de solicitudes asincrónicas para Spring MVC 3.2.
Excepción Manejo
Si está utilizando DeferredResult
,
puede elegir si llamar a setResult
o setErrorResult
con una excepción. En ambos casos,
Spring MVC envía la solicitud de regreso al contenedor de servlets para completar el procesamiento. Luego se trata
como si el método del controlador hubiera devuelto el valor especificado o como si hubiera generado la excepción
especificada. Luego, la excepción pasa por el mecanismo normal de manejo de excepciones (por ejemplo, llamar a
métodos anotados con @ExceptionHandler
).
Cuando se utiliza Callable
, se realiza un
procesamiento similar ocurre la lógica, pero la principal diferencia es si el resultado se devuelve desde el Callable
o si arroja una excepción.
Interceptación
Las instancias HandlerInterceptor
pueden
tener el tipo AsyncHandlerInterceptor
para recibir una devolución de llamada
afterConcurrentHandlingStarted en la solicitud inicial que desencadena el procesamiento asincrónico (en lugar de
postHandle y afterCompletion).
HandlerInterceptor
las implementaciones también pueden
registrar CallableProcessingInterceptor
o DeferredResultProcessingInterceptor
para una
integración más profunda con el ciclo de vida de la solicitud asincrónica (por ejemplo, para manejar un evento de
tiempo de espera). Para obtener más información, consulte la sección sobre el destino AsyncHandlerInterceptor
.
DeferredResult
proporciona
onTimeout(Runnable)
y callbacks onCompletion(Ejecutable)
. Ver detalles en javadoc por DeferredResult
. Callable
se puede reemplazar con
WebAsyncTask
, que expone métodos adicionales para el tiempo de espera y la devolución de llamada de
finalización.
En comparación con WebFlux
El servlet La API se diseñó originalmente para realizar un
solo paso a lo largo de la cadena de servlet de filtro. El procesamiento de solicitudes asincrónico, agregado en
Servlet 3.0, permite que las aplicaciones salgan de la cadena de filtro-servlet pero dejen la respuesta abierta para
su posterior procesamiento. El soporte asíncrono de Spring MVC se basa en este mecanismo. Cuando el controlador
devuelve DeferredResult
, la cadena de filtro-servlet completa la ejecución y se libera el subproceso
del contenedor de servlet. Más tarde, cuando se establece DeferredResult
, se realiza un envío ASYNC
(a la misma URL), durante el cual el controlador vuelve a coincidir, pero en lugar de llamar al valor de
DeferredResult
(como si el controlador lo hubiera devuelto) para reanudar el procesamiento.
Por el contrario, Spring WebFlux no está construido sobre la API de Servlet y no necesita esta función de procesamiento de solicitudes asincrónicas, ya que es asincrónico por definición. El procesamiento asincrónico está integrado en todos los contratos marco y es inherentemente compatible en todas las etapas del procesamiento de solicitudes.
Desde la perspectiva del modelo de programación, tanto Spring MVC como Spring WebFlux admiten tipos asincrónicos y reactivos como valores de retorno en el controlador. métodos. Spring MVC incluso admite subprocesos, incluida la reacción reactiva. Sin embargo, las escrituras individuales en respuesta siguen bloqueando (y se ejecutan en un hilo separado), a diferencia de WebFlux, que se basa en E/S sin bloqueo y no necesita un hilo adicional para cada escritura.
Otro fundamental La diferencia es que Spring MVC no admite tipos asíncronos o reactivos en los
argumentos del método del controlador (por ejemplo, @RequestBody
, @RequestPart
y otros), y
tampoco tiene soporte explícito para tipos asíncronos y reactivos como atributos del modelo. Spring WebFlux admite
todo lo anterior.
Transmisión HTTP
Puede usar DeferredResult
y Callable
para un único valor de retorno asincrónico. ¿Qué sucede si necesita recibir varios valores asincrónicos y volver a
escribirlos? Esta sección describe cómo hacer esto.
Objetos
Puede usar el valor de retorno de ResponseBodyEmitter
para crear una secuencia de objetos, donde cada objeto está serializado usando HttpMessageConverter
y
se escribe en la respuesta, como se muestra en el siguiente ejemplo:
@GetMapping("/events")
public ResponseBodyEmitter handle() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
// Guarda el remitente (emisor) en algún lugar...
return emitter;
}
// En otro hilo
emitter.send("Hello once");
// y luego otra vez
emitter.send("Hello again");
// y en algún momento estará listo
emitter.complete();
@GetMapping( "/events")
fun handle() = ResponseBodyEmitter().apply {
// Save the sender (emitter) somewhere...
}
// En otro hilo
emitter.send("Hello once")
// y luego emitter nuevamente
.send("Hello again")
// y en algún momento estará listo
emitter.complete()
También puedes usar ResponseBodyEmitter
como cuerpo en ResponseEntity
, lo
que le permitirá personalizar el estado de la respuesta y los encabezados.
Cuando el emisor
arroja IOException
(por ejemplo, si el cliente remoto ha desaparecido), las aplicaciones no son
responsables de limpiar la conexión y no deben llamar a emitter.complete
o emitter.completeWithError
.
En cambio, el contenedor de servlets activa automáticamente una notificación de error AsyncListener
en la que Spring MVC realiza una llamada a completeWithError
. Esta llamada, a su vez, realiza el envío
ASYNC
final a la aplicación, durante el cual Spring MVC llama a los controladores de excepciones
configurados y completa la solicitud.
SSE
SseEmitter
(una subclase de ResponseBodyEmitter
)
proporciona soporte para Server -Eventos enviados
si los eventos enviados por el servidor están formateados según la especificación W3C SSE. Para crear una
transmisión SSE desde un controlador, devuelva un SseEmitter
como se muestra en el siguiente ejemplo:
@GetMapping(path="/events ", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {
SseEmitter emitter = new SseEmitter();
// Guarda el remitente (emisor) en algún lugar...
return emitter;
}
// En otro hilo
emitter.send("Hello once");
// y luego otra vez
emitter.send("Hello again");
// y en algún momento
emitter.complete();
@GetMapping( "/events", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun handle() = SseEmitter().apply {
// Guarda el remitente (emisor) en algún lugar...
}
// En otro hilo
emitter.send("Hello once ")
// y luego otra vez
emitter.send("Hello again")
// y en algún estará listo
emitter.complete()
Mientras SSE es la opción principal para transmitir a navegadores; tenga en cuenta que Internet Explorer no admite eventos enviados por el servidor. Considere la posibilidad de utilizar la mensajería del protocolo WebSocket de Spring con mecanismos alternativos del protocolo SockJS (incluido SSE), que están diseñados para una amplia gama de navegadores.
Datos sin procesar
A veces es útil evitar el mensajes de
conversión y transmitirlos directamente a la respuesta OutputStream
(por ejemplo, al descargar un
archivo). Para hacer esto, puede usar el tipo de retorno StreamingResponseBody
, como se muestra en el
siguiente ejemplo:
@GetMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
// escribir...
}
};
}
@GetMapping("/download")
fun handle() = StreamingResponseBody {
// escribir ...
}
Puede utilizar StreamingResponseBody
como cuerpo en ResponseEntity
para configurar
el estado y encabezados de respuesta.
Tipos reactivos
Spring MVC admite el uso de bibliotecas de
cliente reactivas en el controlador. Estas bibliotecas incluyen WebClient
de
spring-webflux
y otras, como los repositorios de datos reactivos del proyecto Spring Data. En tales
escenarios, es conveniente poder devolver tipos reactivos desde un método de controlador.
Los valores de retorno reactivos se manejan de la siguiente manera:
A La promesa de valor único se ajusta como si se usara
DeferredResult
. Los ejemplos incluyenMono
(Reactor) oSingle
(RxJava).Transmisión de valores múltiples con datos de tipo de medios de transmisión ( por ejemplo,
application/x-ndjson
otext/event-stream
) se ajusta, como cuando se usaResponseBodyEmitter
oSseEmitter
. Algunos ejemplos sonFlux
(Reactor) oObservable
(RxJava). Las aplicaciones también pueden devolverFlux<ServerSentEvent>
oObservable<ServerSentEvent>
.Transmisión de valores múltiples con cualquier otro medio Los datos de tipo (por ejemplo,
application/json
) se ajustan, como cuando se usaDeferredResult<List<?>>
.
ReactiveAdapterRegistry
de
spring-core
, lo que le permite adaptarse al moverse entre múltiples bibliotecas reactivas.
Se
admite la reacción reactiva para transmitir datos a la respuesta, pero la escritura en la respuesta aún se bloquea y
se ejecuta en un hilo separado a través de un TaskExecutor
configurado para evitar bloquear la fuente
del hilo ascendente (por ejemplo, Flux
devuelto desde WebClient
). El valor predeterminado
para bloquear la entrada es SimpleAsyncTaskExecutor
, pero no es adecuado para cargar. Si planea usar
una transmisión con un tipo reactivo, entonces debe usar la configuración MVC para configurar el ejecutor.
Conexión perdida
La API de Servlet no envía ninguna notificación cuando el control remoto El cliente desaparece. Por lo tanto, al transmitir una respuesta, ya sea a través de SseEmitter o de tipos reactivos, es importante enviar datos periódicamente, ya que no se escribirá si el cliente se ha desconectado. El envío podría tomar la forma de un evento SSE vacío (solo comentarios) o cualquier otro dato que la otra parte tendría que interpretar como un mensaje de latido e ignorar.
También puede utilizar soluciones de mensajería web (por ejemplo, (por ejemplo, STOMP sobre WebSocket o WebSocket con SockJS), que tienen un mecanismo incorporado para transmitir mensajes de latido.
Configuración
La función de procesamiento de solicitudes asincrónicas debe estar habilitada en el contenedor de servlets. nivel. La configuración de MVC también abre varias opciones para solicitudes asincrónicas.
Contenedor de servlet
Las declaraciones de filtro y servlet tienen un
indicador asyncSupported
que debe establecerse en true
para habilitar el procesamiento de
solicitudes asincrónicas. Además, se deben declarar asignaciones de filtro para manejar ASYNC
javax.servlet.DispatchType
.
En la configuración de Java, cuando usa AbstractAnnotationConfigDispatcherServletInitializer
para inicializar el contenedor de servlets , esto sucede automáticamente.
En la configuración de web.xml
,
puede agregar <async-supported>true</async-supported>
a DispatcherServlet
y
declaraciones Filter
, y agregue <dispatcher>ASYNC</dispatcher>
para filtrar
asignaciones.
Spring MVC
La configuración de MVC proporciona las siguientes opciones relacionadas con el procesamiento de solicitudes asincrónicas:
Configuración de Java: use la devolución de llamada
configureAsyncSupport
paraWebMvcConfigurer
.Espacio de nombres XML: use el elemento
<async-support>
en<mvc:annotation-driven>
sección.
Se pueden configurar los siguientes parámetros:
El valor de tiempo de espera predeterminado para solicitudes asincrónicas, que, si no se configura, depende del contenedor de servlet subyacente.
AsyncTaskExecutor
se utilizará con fines de bloqueo de escritura cuando se transmitan datos mediante tipos reactivos y para ejecutar instanciasCallable
devueltas por los métodos del controlador. Recomendamos encarecidamente configurar esta propiedad si está trabajando con tipos reactivos o tiene métodos de controlador que devuelvenCallable
, ya que el valor predeterminado esSimpleAsyncTaskExecutor
.Implementaciones de DeferredResultProcessingInterceptor
e implementaciones deCallableProcessingInterceptor
.
Tenga en cuenta que también es posible configurar el valor de tiempo de espera predeterminado para
DeferredResult
, ResponseBodyEmitter
y SseEmitter
. Para Callable
puede usar WebAsyncTask
para especificar el valor del tiempo de espera.
GO TO FULL VERSION