Servlet 3.0 (II) Servlets Asincronos

En el articulo anterior hemos usado las anotaciones de Servlets 3.0 para dar de alta un servlet sin tener la necesidad de hacer uso del web.xml.En este articulo introduciremos el concepto de servlet asincrono . Supongamos que disponemos del siguiente servlet.

package com.arquitecturajava;

//omitimos imports

/**
 * Servlet implementation class HolaMundo
 */
@WebServlet("/HolaMundo")
public class HolaMundo extends HttpServlet {
 private static final long serialVersionUID = 1L;

 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

 PrintWriter pw= response.getWriter();
 pw.println("<html>");
 pw.println("<body>");

 for (int i = 0; i < 10; i++) {
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {

 e.printStackTrace();
 }
 }

 pw.println("hola mundo");
 pw.println("</body>");
 pw.println("</html>");
 }
}

Como se puede ver el servlet  tiene un buble for que le obliga a dormir durante un total de 10 s simulando una tarea que consume recursos y lleva bastante tiempo ejecutar.
Al tardar 10 segundos en ejecutar la tarea el usuario no recibirá información alguna sobre que es lo que ha sucedido  y le dará la sensación que la aplicación ha fallado.Para evitar esta situación podemos construir un servlet asincrono .Este nuevo tipo de servlet se declara de la siguiente manera.


package com.arquitecturajava;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "HolaMundo", urlPatterns = { "/HolaMundo" },
asyncSupported = true)
public class HolaMundo extends HttpServlet {

private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request,
 HttpServletResponse response) throws ServletException, IOException {
 final PrintWriter pw = response.getWriter();
 pw.println("<html>");
 pw.println("<body>");
 pw.println("hola mundo asincrono tarea realizandose en background");
 System.out.println("Hilo Principal:" + Thread.currentThread().getName());
 final AsyncContext contextoAsincrono = request.startAsync();
 contextoAsincrono.setTimeout(12000);

contextoAsincrono.start(new Runnable() {
 @Override
 public void run() {
 for (int i = 0; i <= 10; i++) {
 System.out.println("Hilo Tarea Asincrona :"
 + Thread.currentThread().getName());
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 }
 }
 contextoAsincrono.complete();

}
 });
 pw.println("</body>");
 pw.println("</html>");
 pw.close();

}

}

Es muy similar a un servlet clásico con la peculiaridad de que soporta un nuevo atributo “asyncSupported = true”. Vamos a comentar mas a detalle el siguiente bloque de código del método doGet.

pw.println("hola mundo asincrono tarea realizandose en background");
System.out.println("Hilo Principal:" + Thread.currentThread().getName());
final AsyncContext contextoAsincrono = request.startAsync();
contextoAsincrono.setTimeout(12000);

contextoAsincrono.start(new Runnable() {
 @Override
 public void run() {
 for (int i = 0; i <= 10; i++) {
 System.out.println("Hilo Tarea Asincrona :"
 + Thread.currentThread().getName());
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 }
 }
 contextoAsincrono.complete();

}
 });

El método en cuestión modifica la forma de ejecutar el servlet a traves de la siguiente linea de código

final AsyncContext contextoAsincrono = request.startAsync();

Esta linea define un contexto de ejecución asincrono para que mas adelante podemos pasar a este contexto un objeto de tipo Runnable  (para lanzar un thread en paralelo)

contextoAsincrono.start(new Runnable() {

Una vez hechas estas dos operaciones ,el contexto lanzara un nuevo thread para ejecutar  la tarea que nosotros le hemos solicitado de forma asincrona. Sin penalizar al thread actual que esta ejecutando el método doGet del servlet

A continuación podemos ver en la consolo de Tomcat  como el Servlet se ejecuta en el hilo principal  (hilo 5)y la tarea asincrona en otro hilo (hilo 6).

About Cecilio Álvarez Caules

Cecilio Álvarez Caules Sun Certified Enterprise Architech (J2EE/JEE).

Subscribe

Síguenos en Linkedin y Twitter o subscríbete al RSS.

,

7 Responses to Servlet 3.0 (II) Servlets Asincronos

  1. José Luis Villaverde 23 febrero, 2013 at 18:34 #

    En estos momentos estoy estudiando las especifiaciones de Servlets 3.0, estoy mirando el tema de los servlets asincronos. Lo que muestras es muy aclaratorio pero tengo dudas como buen principiante.

    Entiendo que el código que desarrollas en el servlet tiene como función clarificar el tema, pero, en un entorno real, aquí mis dudas, ¿debemos decidir a través del código que peticiones entran en el entorno asíncrono y cuales no(respuesta instantánea)? ¿o todas las peticiones se canalizan a través del entorno asíncrono?

    Espero haberme explicado bien.

    • Cecilio Álvarez Caules 24 febrero, 2013 at 15:26 #

      Normalente las tareas sincronas las realizarás con un servlet normal (sincrono) . Mientras que algunas tareas especiales (de larga duración) las delegaras en un servlet asincrono.Espero te aclaré la pregunta.

  2. José Luis Villaverde 23 febrero, 2013 at 19:53 #

    Creo que ya tengo claro el tema.
    Lo que se consigue con los servlets asíncronos es que las peticiones (tareas a realizar) se procesen en un entorno asíncrono , en threads distinos del thread principal. Con esto conseguimos que el thread principal no se bloquee y retorne al contenedor a encargarse de otras tareas.
    ¿es asi?
    Entiendo que estas tareas a realizar en el entorno asíncrono tanto pueden devolver un response al navegador para visivilizar datos como realizar otro tipo de funciones.
    ¿?

  3. Pedro 1 julio, 2013 at 16:26 #

    Hola Cecilio, he copiado el ejemplo, pero no consigo hacerlo funcionar, alguna pista?

    jul 01, 2013 5:21:15 PM org.apache.catalina.core.StandardWrapperValve invoke
    SEVERE: Servlet.service() para servlet HolaMundo2Asincrono lanzó excepción
    java.lang.IllegalStateException: Not supported.
    at org.apache.catalina.connector.Request.startAsync(Request.java:1673)
    at org.apache.catalina.connector.Request.startAsync(Request.java:1666)
    at org.apache.catalina.connector.RequestFacade.startAsync(RequestFacade.java:1023)
    at javax.servlet.ServletRequestWrapper.startAsync(ServletRequestWrapper.java:364)
    at es.dbapps.HolaMundo2Asincrono.doGet(HolaMundo2Asincrono.java:32)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:393)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:936)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1004)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)

    Uso Nebeans 7.3.1 y el tomcat que viene incorporado Tomcat/7.0.34

    • Pedro 1 julio, 2013 at 16:40 #

      En eclipse si funcionó. Algo le pasa al Netbeans, pero vamos está sin tunear ni nada, tal cual lo instale.

    • Cecilio Álvarez Caules 20 julio, 2013 at 9:10 #

      Simplemente tu servidor no soporta la especificación . Comprueba que tu servidor soporta Servlet 3.0

  4. Pedro Andrés Díaz Gómez 6 septiembre, 2016 at 16:47 #

    El ejemplo que pones es muy claro pero me gustaría hacer ampliaciones; por ejemplo ¿Cómo se haría para que una variable puesta en el for de pausa metido en el método run() (que imita la tarea pesada) imprima la variable de ese contador, pero no en el bucle, sino en la salida (response), justgo después de la llamada al método complete().

    Por de pronto, la variable del for, no podría ser local al for y tampoco conviene variable miembro (por compartirse entre todos los servlets) y si la hago variable local (por encima del for) no compila por decirme que debía ser constante ¿?…

    Lo que pretendo probar es que una variable contador del for puesto en el método run del hilo, se muestre en la salida de la página html y ver qué valor tendría para ver si espera a alcanzar su último valor o si imprime uno intermedio para ver cómo exactamente gestiona esos retrasos y cuellos de botella. Espero haber estado claro.

Deja un comentario