spring-mvc - method - spring boot run async
Diferencia entre Spring MVC''s @Async, DeferredResult y Callable (3)
Tengo una tarea de larga duración definida en un servicio Spring. Es iniciado por un controlador Spring MVC. Quiero iniciar el servicio y devolver un HttpResponse
a la persona que llama antes de que finalice el servicio. El servicio guarda un archivo en el sistema de archivos al final. En javascript he creado un trabajo de sondeo para verificar el estado del servicio.
En la primavera 3.2 encontré la anotación @Async
, pero no entiendo en qué se diferencia de DeferredResult
y Callable
. ¿Cuándo debo usar @Async
y cuándo debo usar DeferredResult
?
Su controlador es finalmente una función ejecutada por el subproceso de trabajo del contenedor de servlets (asumiré que es Tomcat). Su flujo de servicio comienza con Tomcat y termina con Tomcat. Tomcat recibe la solicitud del cliente, mantiene la conexión y, finalmente, devuelve una respuesta al cliente. Su código (controlador o servlet) está en algún lugar en el medio.
Considera este flujo:
- Tomcat obtener solicitud de cliente.
- Tomcat ejecuta su controlador.
- Libere el subproceso de Tomcat pero mantenga la conexión del cliente (no devuelva la respuesta) y ejecute el procesamiento pesado en un subproceso diferente.
- Cuando finalice su procesamiento pesado, actualice Tomcat con su respuesta y devuélvalo al cliente (por Tomcat).
Debido a que el servlet (su código) y el contenedor del servlet (Tomcat) son entidades diferentes, entonces para permitir este flujo (liberando el subproceso Tomcat pero manteniendo la conexión del cliente) necesitamos tener este soporte en su contrato , el paquete javax.servlet
, que introducido en Servlet 3.0 . Ahora, volviendo a su pregunta, Spring MVC usa la nueva capacidad Servlet 3.0 cuando el valor de retorno del controlador es DeferredResult
o Callable
, aunque son dos cosas diferentes. Callable
es una interfaz que forma parte de java.util
, y es una mejora para la interfaz Runnable
(debe ser implementada por cualquier clase cuyas instancias estén destinadas a ser ejecutadas por un hilo). Callable
permite devolver un valor, mientras que Runnable
no lo hace. DeferredResult
es una clase diseñada por Spring para permitir más opciones (que describiré) para el procesamiento de solicitudes asíncronas en Spring MVC, y esta clase solo tiene el resultado (como lo Callable
su nombre), mientras que su implementación de Callable
tiene el código async. Por lo tanto, significa que puede usar ambos en su controlador, ejecute su código asíncrono con Callable
y establezca el resultado en DeferredResult
, que será el valor de retorno del controlador. Entonces, ¿qué obtiene al usar DeferredResult
como el valor de retorno en lugar de Callable? DeferredResult
tiene devoluciones de llamada onError
como onError
, onTimeout
y onCompletion
. Hace que el manejo de errores sea muy fácil. Además, como solo es el contenedor de resultados, puede elegir cualquier subproceso (o grupo de subprocesos) para ejecutar en su código asíncrono. Con Callable, no tienes esta opción.
En cuanto a @Async
, es mucho más simple: la anotación de un método de un bean con @Async
hará que se ejecute en un hilo separado. De forma predeterminada (se puede anular), Spring utiliza un SimpleAsyncTaskExecutor
para ejecutar estos métodos de forma asíncrona.
En conclusión, si desea liberar el subproceso de Tomcat y mantener la conexión con el cliente mientras realiza un procesamiento intenso, su controlador debería devolver Callable
o DeferredResult
. De lo contrario, puede ejecutar el código en el método anotado con @Async
.
DeferredResult
aprovecha el Servlet 3.0 AsyncContext. No bloqueará el hilo como lo harán los demás cuando necesite que se devuelva un resultado.
Otro gran beneficio es que DeferredResult
soporta devoluciones de llamada.
Async anota un método por lo que se va a llamar de forma asíncrona.
@org.springframework.stereotype.Service
public class MyService {
@org.springframework.scheduling.annotation.Async
void DoSomeWork(String url) {
[...]
}
}
Así que Spring podría hacerlo, por lo que necesita definir cómo se ejecutará. Por ejemplo:
<task:annotation-driven />
<task:executor id="executor" pool-size="5-10" queue-capacity="100"/>
De esta manera, cuando llama a service.DoSomeWork ("parámetro"), la llamada se pone en la cola del ejecutor para que se llame de forma asíncrona. Esto es útil para tareas que podrían ejecutarse simultáneamente.
Podría utilizar Async para ejecutar cualquier tipo de tarea asíncrona. Si lo que desea es llamar a una tarea periódicamente, puede usar @Scheduled (y usar task: scheduler en lugar de task: executor). Son formas simplificadas de llamar java Runnables.
DeferredResult <> se utiliza para responder a una petición sin bloquear el subproceso HTTP de Tomcat utilizado para responder. Por lo general, será el valor de retorno para un método anotado de ResponseBody.
@org.springframework.stereotype.Controller
{
private final java.util.concurrent.LinkedBlockingQueue<DeferredResult<String>> suspendedRequests = new java.util.concurrent.LinkedBlockingQueue<>();
@RequestMapping(value = "/getValue")
@ResponseBody
DeferredResult<String> getValue() {
final DeferredResult<String> result = new DeferredResult<>(null, null);
this.suspendedRequests.add(result);
result.onCompletion(new Runnable() {
@Override
public void run() {
suspendedRequests.remove(result);
}
});
service.setValue(result); // Sets the value!
return result;
}
}
El ejemplo anterior carece de una cosa importante y es que no muestra cómo se establecerá el resultado diferido. En algún otro método (probablemente el método setValue) habrá un resultado. SetResult (valor). Después de que la llamada a setResult Spring llame al procedimiento onCompletion y devuelva la respuesta a la solicitud HTTP (consulte https://en.wikipedia.org/wiki/Push_technology#Long_polling ).
Pero si solo está ejecutando setValue de forma síncrona, no hay ninguna ventaja en el uso de un resultado diferido. Aquí es donde Async viene en la mano. Podría usar un método asíncrono para establecer el valor de retorno en algún momento en el futuro utilizando otro hilo.
@org.springframework.scheduling.annotation.Async
void SetValue(DeferredResult<String> result) {
String value;
// Do some time consuming actions
[...]
result.setResult(value);
}
Async no es necesario para usar un resultado diferido, es solo una forma de hacerlo.
En el ejemplo, hay una cola de resultados diferidos que, por ejemplo, una tarea programada podría monitorear para procesar sus solicitudes pendientes. También puede utilizar algún mecanismo de bloqueo (consulte http://en.wikipedia.org/wiki/New_I/O ) para establecer el valor de retorno.
Para completar la imagen, puede buscar información sobre futuros estándar de Java ( http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Future.html ) y callables ( http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Callable.html ) que son algo equivalentes a Spring DeferredResult y Async.