Tareas programadas con Spring y Quartz

En un post anterior explicamos cómo utilizar las clases wrapper de Spring para ejecutar una tarea a intervalos regulares de tiempo dentro de una aplicación web J2EE. En esta publicación integraremos Spring con el planificador Quartz para programar la misma tarea dentro de una aplicación J2EE. Quartz es un servicio opensource de planificación de tareas que puede ser integrado con cualquier aplicación Java, desde la más pequeña aplicación stand-alone al más extenso sistema de e-commerce

En un post anterior explicamos cómo utilizar las clases wrapper de Spring para ejecutar una tarea a intervalos regulares de tiempo dentro de una aplicación web J2EE. En esta publicación integraremos Spring con el planificador Quartz para programar la misma tarea dentro de una aplicación J2EE.

Quartz es un servicio opensource de planificación de tareas que puede ser integrado con cualquier aplicación Java, desde la más pequeña aplicación stand-alone al más extenso sistema de e-commerce. Quartz puede ser utilizado para crear planificaciones simples o complejas para ejecuciones de decenas, cientos, o incluso miles de trabajos cuyas tareas son definidas como componentes Java estándar. Además incluye soporte para transacciones JTA y clustering. Es de uso libre bajo la licencia Apache 2.0.

Para instalarla en nuestra aplicación simplemente descargaremos el fichero quartz-x.x.x.tar.gz desde la web de descargas y extraeremos el fichero quartz-all-x.x.x.jar para incorporarlo a nuestra aplicación. En mi caso utilicé la versión 1.8.6, pese a no ser la última, debido a mi versión de Spring. Si tienes problemas con la última, te recomiendo que vayas bajando de versión hasta dar con la idónea. Dentro del fichero comprimido tar.gz existe una carpeta lib con las dependencias que tiene quartz con otras librerías. En mi caso necesité incluir en mi aplicación la librería slf4j-api-x.x.x.jar.

Lo primero que vamos a crear es la tarea a ejecutar. Será un método público dentro de una clase pública, tal y como se muestra en el siguiente código:

package es.activity.schedule;

public class RunMeTask
{
	public void printMe() {
		System.out.println("Ejecutando tarea programada");
	}
}

A continuación abrimos para editar el fichero XML de contexto de nuestra aplicación web Spring y añadimos un bean que haga referencia a la clase anterior:

<bean id="runMeTask" class="es.activity.schedule.RunMeTask" />

Una vez definida la tarea pasamos a configurar los principales conceptos de Quartz: Job, Trigger y Scheduler:

Job

Es la unidad de trabajo o tarea a ejecutar. Sería cualquier implementación de la interfaz org.quartz.Job. En Spring se han incluido varias implementaciones de la interfaz pero nosotros vamos a utilizar MethodInvokingJobDetailFactoryBean, la cual nos permite la ejecución de un método de cualquier clase java:

<bean id="schedulerJob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
   <property name="targetObject" ref="runMeTask"></property>
   <property name="targetMethod" value="printMe"></property>
   <property name="concurrent" value="false"></property>
</bean>

Trigger

Es la unidad que conoce los detalles de la ejecución de la tarea y los instantes en los que se debe ejecutar. Sería cualquier implementación de la interfaz org.quartz.Trigger. Es este caso es el propio Quartz el que proporciona diversas implementaciones:

SimpleTrigger

Permite especificar una hora de comienzo, una hora de fin y un intervalo de ejecución, similar al ejemplo del post en el que hablábamos sobre la clase Timer incluida en el JDK. En el ejemplo ejecutamos el job tras un segundo de retardo y posteriormente cada 60 segundos.

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
        <property name="jobDetail" ref="schedulerJob" />
        <property name="repeatInterval" value="60000" />
        <property name="startDelay" value="1000" />
</bean>

CronTrigger

Permite especificar expresiones cron de Unix para establecer fechas y horas de ejecución del job. En el ejemplo se ejecuta el job de lunes a viernes a las 8 de la mañana.

<bean id="timerCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">>
        <property name="jobDetail" ref="schedulerJob"></property>
        <property name="cronExpression" value="0 0 8 ? * MON-FRI"></property>
</bean>

Más información sobre expresiones cron en los siguientes enlaces:

  1. http://en.wikipedia.org/wiki/CRON_expression
  2. http://www.quartz-scheduler.org/documentation/quartz-1.x/examples/Example3

Scheduler

Por último tenemos que lanzar la ejecución de nuestro job mediante un planificador que gestione la ejecución a partir de la información especificada en el Trigger. Spring incorpora la clase SchedulerFactoryBean para ejecutar uno o varios trigger:

<bean id="GiaScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="timerCronTrigger"></ref>
            </list>
        </property>
</bean>

Con todo esto especificado en el fichero de contexto xml, desplegamos la aplicación en el servidor de aplicaciones y se ejecutará la tarea con la programación especificada.

 

Tareas programadas con Spring Framework y JDK Timer

Seguro que en más de una ocasión, durante el desarrollo de una aplicación has necesitado ejecutar una tarea de forma periódica. Para desarrollar esto, desde la versión 1.3 del JDK se incluye las clases java.util.Timer y java.util.TimerTask. Estas clase facilitan la programación de tareas para ejecuciones futuras en un thread en segundo plano.

Seguro que en más de una ocasión, durante el desarrollo de una aplicación has necesitado ejecutar una tarea de forma periódica. Para desarrollar esto, desde la versión 1.3 del JDK se incluye las clases java.util.Timer y java.util.TimerTask. Estas clase facilitan la programación de tareas para ejecuciones futuras en un thread en segundo plano. Las tareas pueden ser programadas para ejecutarse una vez o múltiples veces a intervalos regulares.

En el caso de que necesites una planificación más compleja, la clase Timer se quedará corta en varios aspectos y deberás utilizar un planificador como Quartz o similar. En este hilo de StackOverflow puedes ver otras alternativas. En este artículo vamos a utilizar las clases wrapper de Spring, que internamente hacen uso de las del JDK, para ejecutar una tarea a intervalos regulares de tiempo dentro de una aplicación web J2EE con el framework de Spring.

Lo primero que vamos a crear es la tarea a ejecutar. Será un método público dentro de una clase pública, tal y como se muestra en el siguiente código:

package es.activity.schedule;

public class RunMeTask
{
	public void printMe() {
		System.out.println("Ejecutando tarea programada");
	}
}

A continuación abrimos para editar el fichero XML de contexto de nuestra aplicación web Spring y añadimos un bean que haga referencia a la clase anterior:

<bean id="runMeTask" class="es.activity.schedule.RunMeTask" />

Vamos a definir a continuación, en el mismo fichero, el método de la clase que ejecutará el código de la tarea. Spring incorpora la clase MethodInvokingTimerTaskFactoryBean para reemplazar la clase TimerTask del JDK.

<bean id="schedulerTask" 
  class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean">
	<property name="targetObject" ref="runMeTask" />
	<property name="targetMethod" value="printMe" />
</bean>

Ahora vamos a definir cada cuánto se ejecutará utilizando la clase ScheduledTimerTask, que reemplaza a la clase Timer del JDK. Le pasamos como propiedades al bean tanto el retardo (delay) para la primera ejecución, como el intervalo (period) para sucesivas ejecuciones. Ambos valores en milisegundos. En el ejemplo se ejecutará un segundo después del despliegue de la aplicación y a continuación cada 60 segundos.

<bean id="timerTask"
	class="org.springframework.scheduling.timer.ScheduledTimerTask">
	<property name="timerTask" ref="schedulerTask" />
	<property name="delay" value="1000" />
	<property name="period" value="60000" />
</bean>

Hasta ahora se trata de configurar de manera declarativa lo referente a la tarea. Vamos a ejecutar el timer para que comience la ejecución:

<bean class="org.springframework.scheduling.timer.TimerFactoryBean">
	<property name="scheduledTimerTasks">
		<list>
			<ref local="timerTask" />
		</list>
	</property>
</bean>

Cómo ves no es necesario introducir ninguna llamada por código a la tarea programada. La clase TimerFactoryBean se encarga de ejecutar el código del método PrintMe() durante el despliegue de la aplicación en el servidor, con un retardo de 1 segundo la primera vez y luego cada 60 segundos.

Actualización (24/01/2013). Para completar la información quizás te interese cómo integrar Spring con el planificador Quartz. Por ello publiqué otro artículo posteriormente con el título “Tareas programadas con Spring y Quartz”.

Introducción a Aplicaciones REST con Spring 3.0 Web MVC Framework

El Framework Web MVC (modelo-vista-controlador) de Spring está diseñado alrededor de un Servlet (DispatcherServlet) encargado de enviar las peticiones a los diferentes manejadores (handlers) de la aplicación, a partir de la configuración del mapeo de manejadores, la resolución de las vistas, locale, etc. Es decir, al configurar este serlvet en el fichero web.xml de tu aplicación web, todas las peticiones pasarán por él y se encargará de redirigir a los diferentes manejadores. Los manejadores por defecto se basan en las anotaciones @Controller y @RequestMapping como veremos a continuación. A partir de la versión 3.0 de Spring es posible crear aplicaciones Web RESTfull a través de la anotación @PathVariable.

En esto último se va a centrar este artículo, en la implementación de los controladores a través de anotaciones y en la configuración de los mismos para crear aplicaciones REST.

Dispatcher Servlet

Cómo comentamos al principio, el framework MVC de Spring está diseñado alrededor de un servlet que reenvía las peticiones a los diferentes controladores. Este servlet está plenamente integrado con el contenedor IoC (Inversion of Control) lo que permite usar todas las características del framework Spring.

Workflow del Servlet

Se trata de un servlet (hereda de la clase base HttpServlet) y como tal se declara en el fichero de configuración web.xml de tu aplicación web:

<web-app>

    <servlet>
        <servlet-name>buscar</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>buscar</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

</web-app>

En el ejemplo anterior, tal como se puede ver en el mapeo del servlet, todas las peticiones que acaben en .do serán manejados por el DispatcherServlet buscar.

En el framework Web MVC cada DispatcherServlet tiene asociado su propio WebApplicactionContext, el cuál hereda todos los beans definidos en el WebApplicationContext raíz que pueden ser sobreescritos. Una vez inicializado el DispatcherServlet, el framework busca un fichero llamado [servlet-name]-servlet.xml en el directorio WEB-INF de tu aplicación web y crea los beans allí definidos (si hay beans con el mismo nombre que los del contexto raíz los sobreescribe). En el ejemplo anterior debemos tener un fichero llamado buscar-servlet.xml.

El DispatcherServlet de Spring usa beans especiales para procesar las peticiones y mostrar las vistas apropiadas. Estos beans son parte del Framework y pueden ser configurados en el contexto WebApplicationContext como harías con otros beans. Sin embargo, para la mayoría de los beans se proporcionan parámetros por defecto y no necesitan configuración.

Contexto del DispatcherSerlvet

Implementando Controladores

Los controladores proveen acceso al comportamiento de la aplicación, interpretan los inputs del usuario y los transforman en un modelo que es representado al usuario por una vista (modelo-vista-controlador). Spring implementa un controller de una manera abstracta, lo que permite crear una gran variedad de controladores.

A partir de la versión 2.5, y la mantiene también en la versión 3.0, Spring introdujo el modelo de programación basado en anotaciones, que usa anotaciones como @RequestMapping, @RequestParam, @ModelAttribute, etc. Los controladores implementados a través de este estilo no necesitan heredar de ninguna clase base o implementar ninguna interfaz específica:

@Controller
public class HelloWorldController {

    @RequestMapping("/helloWorld")
    public ModelAndView helloWorld() {
        ModelAndView mav = new ModelAndView();
        mav.setViewName("helloWorld");
        mav.addObject("message", "Hello World!");
        return mav;
    }
}

Definiendo un controlador con @Controller

La anotación @Controller indica que una clase tendrá el rol Controlador. No se requiere heredar de ninguna clase base o del API de Servlet, como ocurría en la versión 2.0. El Dispatcher busca las clases “marcadas” con dicha anotación para mapear los métodos y detectar todas las anotaciones @RequestMapping (ver más abajo).

Para activar la autodetección de los controllers que contienen la anotación, debes añadir a tu configuración el componente que los escanea:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="com.paquete.ejemplo.controllers"/>

    // ...

</beans>

Mapeando peticiones con @RequestMapping

Se usa la anotación @RequestMapping para mapear URLs como por ejemplo “/mostrareventos” con una clase, o bien un método dentro de la clase. Si es a nivel de clase, se suele mapear una URL concreta con métodos adicionales que acotan el mapeo a través de métodos HTTP específicos (POST, GET, etc.) o bien mediante parámetros en la petición.

@Controller
@RequestMapping("/mostrareventos")
public class AppointmentsController {

    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @RequestMapping(method = RequestMethod.GET)
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @RequestMapping(value="/{dia}", method = RequestMethod.GET)
    public Map<String, Appointment> getForDay(
                @PathVariable @DateTimeFormat(iso=ISO.DATE) Date dia,
                Model model) {
        return appointmentBook.getAppointmentsForDay(dia);
    }

    @RequestMapping(value="/new", method = RequestMethod.GET)
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @RequestMapping(method = RequestMethod.POST)
    public String add(@Valid AppointmentForm evento,
                       BindingResult result) {
        if (result.hasErrors()) {
            return "mostrareventos/new";
        }
        appointmentBook.addAppointment(evento);
        return "redirect:/mostrareventos";
    }
}

En el ejemplo anterior la anotación @RequestMapping se usa primero a nivel de clase. De esa manera indica que todos los métodos de este controlador manejarán las peticiones con el path relativo “/mostrareventos”. El métdo get() refina dicha URL y se ejecuta únicamente cuando se trata de un método GET. El método add(), por el contrario, se ejecuta cuando se realiza un POST de un formulario del sitio web.

Los métodos getForDay() y getNewForm() se ejecutarán cuando se trate de una petición GET y además la URL contenga “/mostrareventos/díaEventoo “/mostrareventos/new”, respectivamente.

URI Templates

Para acceder a partes de la URL en los métodos manejadores usamos los patrones de URI o URI Templates, dando valores dentro del path de @RequestMapping. Luego podemos usar la anotación @PathVariable para indicar que un parámetro del método se va a enlazar con un valor que viene en una variable de la URI. Veamos un ejemplo:

@RequestMapping(value="/alumnos/{alumnoId}", method=RequestMethod.GET)
public String findAlumno(@PathVariable String alumnoId, Model model) {
  Alumno alumno = alumnoService.findAlumno(alumnoId);
  model.addAttribute("alumno", alumno);
  return "displayAlumno";
}

La URI Template “/alumnos/{alumnoId}” especifica la variable con el nombre alumnoId. Cuando un controlador maneja esta petición, el valor de alumnoId se establece con el valor que viene en la URL. Por ejemplo, cuando llega una petición con esta URL “/alumnos/francisco” el valor “francisco” se asociará al parámetro alumnoId de tipo String del método manejador.

Puedes usar varias anotaciones @PathVariable para enlazar múltiples variables URI Templates:

@RequestMapping(value="/alumno/{alumnoId}/asignatura/{asignaturaId}",
                 method=RequestMethod.GET)
public String findAsignatura(@PathVariable String alumnoId,
                 @PathVariable String asignaturaId, Model model) {
  Alumno alu = alumnoService.findAlumno(alumnoId);
  Asignatura asig = alumno.getSignatura(signaturaId);
  model.addAttribute("Asignatura", asignatura);
  return "displayAsignatura";
}

El método findAsignatura se ejecutaría al invocar, por ejemplo la URL “/alumno/245/asignatura/32“. Podemos indicar una parte a nivel de clase y otra a nivel de método como en el siguiente ejemplo:

@Controller
@RequestMapping("/alumno/{alumnoId}")
public class RelativePathUriTemplateController {

  @RequestMapping("/asignatura/{asignaturaId}")
  public void findPet(@PathVariable String alumnoId,
                       @PathVariable String asignaturaId,
                         Model model) {
    // implementación omitida
  }
}

Conclusiones

Tal y cómo nos propusimos al inicio de este artículo pretendíamos realizar una introducción a la programación de aplicaciones REST con Spring 3.0. Cómo cualquier aplicación Spring MVC, hemos configurado el DispatcherServlet, hemos aprendido a declarar controladores mediante la anotación @Controller. También hemos mapeado peticiones con la anotación @RequestMapping y por último hemos asociado valores de variables de la URL a parámetros de los métodos manejadores de los controladores mediante URI Templates y la anotación @PathVariable. Con todo esto podemos comenzar a construir una aplicación REST en la que cada recurso sea accesible mediante una URL amigable del tipo:

http://mi-dominio/gestionalumnos/
http://mi-dominio/gestionalumnos/alumno/new
http://mi-dominio/gestionalumnos/alumno/234
http://mi-dominio/gestionalumnos/alumno/234/asignatura/32
http://mi-dominio/gestionalumnos/asignatura/new
http://mi-dominio/gestionalumnos/asignatura/32
etc...

Este artículo está basado en la documentación de referencia de Spring. Para ampliar estos y otros temas puedes consultar la Referencia del framework Spring MVC.

Tablas con DisplayTag y Ajax

Durante los dos últimos años, mi equipo y yo hemos introducido en los proyectos J2EE una librería de tags JSP para la publicación de datos de forma tabular. Se trata de DisplayTag, una librería open source muy potente que nos facilita la visualización de tablas de datos y la ordenación y paginación de los mismos empleando muy poco código. Para incluirla basta con añadir en la página JSP el tag <display:table>, indicarle el nombre de las columnas, así como el nombre de la colección de datos que enviamos desde el servidor en la respuesta. La librería se encarga de generar el código html para presentar los datos en la tabla, generar los controles de paginación y enlaces de ordenación de las columnas.

Una de las funcionalidades que no incluye esta librería es la utilización de AJAX,  que nos acerca una serie de técnicas de programación para la creación de aplicaciones interactivas. Cada vez que pulsamos para ordenar la tabla o para pasar a la siguiente página de resultados se realiza una petición al servidor y éste devuelve una nueva página recargando la anterior con la nueva información. Sería conveniente que por cada petición de ordenar o cambiar la página de resultados se recargase únicamente la información de la tabla de forma asíncrona y no toda la página.

Solución con Ajax Tags

La librería Ajax Tags es un conjunto de tags JSP que simplifica el uso de la tecnología AJAX en las páginas JSP. Además incorpora un tag específico para permitir y habilitar AJAX en las Displaytags comentadas más arriba. Para utilizarlo basta con envolver la tabla con el tag <ajax:displayTag> de manera que sólo se refresque el área contenida en éste y no toda la página:

<ajax:displayTag id="displayTagFrame" ajaxFlag="displayAjax">

    <display:table name="service.allCars" pagesize="10" scope="page"
        defaultsort="1" defaultorder="descending" export="true" id="row"
        excludedParams="ajax">
        <display:column property="make" title="Make" sortable="true"
            headerClass="sortable" />
        <display:column property="model" title="Model" sortable="true"
            headerClass="sortable" />
        <display:column title="Link" media="html">
            <a href="http://www.${row.make}.com">${row.make} Web Page</a>
        </display:column>
        <display:column title="Link" media="excel xml"> www.${row.make}.com </display:column>
    </display:table>

</ajax:displayTag>

¿Cómo funciona? Engloba todo el código html de la tabla en un div con el id indicado como parámetro, sobreescribe el método onclick de cada enlace de la tabla por un código que se encarga de realizar la llamada al servidor y recargar únicamente el div indicado con el código de la tabla (obviando todo el código que no sea la tabla).

Aun resultando así de fácil nos hemos encontrado dos problemas:

  1. Nosotros utilizamos Struts/Tiles para componer la maquetación de las páginas (aunque nuestro framework sea Spring), de manera que dividimos la página en varias JSPs (cabecera, menú, pie y contenido). Cómo hemos dicho anteriormente ajaxtags obvia el código que no sea de la tabla pero únicamente lo hacía para el contenido, por lo que en el div volvía a aparecer la cabecera, el pie, etc. La solución fue crear una definición en la configuración de Tiles que sólo incluyese el contenido y si en la petición venía cierto parámetro, redirigir desde el servidor a ésta.
  2. Ajaxtags sobreescribe el evento onclick de los enlaces, pero también sobreescribe el atributo href con un javascript://nop/. Esto nos supone un problema si el sitio tiene alguna restricción de accesibilidad, puesto que en navegadores que no tengan javascript activo (o no soporten js) no funcionará la paginación y ordenación de la tabla.

Solución con jQuery

jQuery es una librería Javascript que simplifica el desplazamiento entre documentos, manipulación del DOM, la gestión de eventos, los efectos y animaciones y la interacción mediante Ajax para un rápido desarrollo web. Esta solución se basa en la funcionalidad load de jQuery, la cual permite cargar html externo desde una fuente remota e inyectarlo en el DOM.

Vamos a verlo más fácil con el siguiente ejemplo:

<div id="tablaresultados">

    <display:table name="modelo.resultado"
        requestURI="/catalog/busquedaTexto.do" pagesize="10" cellspacing="0"
        decorator="es.displaytag.decorator.BuscaTextoTableDecorator"
        id="resultados">
        <display:column title="Título" property="titulo" sortable="true" />
        <display:column title="Signatura" property="signatura" sortable="true" />
        <display:column title="Fechas" property="fecha" style="text-align:center" sortable="true" />
    </display:table>

</div>

El único requisito en la tabla será que la contenga una capa con el id que deseemos. En esta solución sí que es necesario introducir código javascript en la página:

<script>

    if (!com) var com = {};
    es.displaytag= {
            onPeopleTableLoad: function() {

                // Se llama cuando se cargan los datos
                $("table#resultados th.sortable").each(function() {
                    // Itera sobre cada cabecera de columna conteniendo la clase sortable, de manera
                    // que
                    // podemos sobreescribir la gestión de click via ajax, en lugar de
                    // permitir al navegador seguir un enlace normal
                    $(this).click(function() {
                        // "this" es el elemento th ordenable
                        var link = $(this).find("a").attr("href") + " #tablaresultados";
                        $("div#tablaresultados").load(link, {}, es.displaytag.onPeopleTableLoad);
                        // Paramos la propagación de eventos, sin permitir que el navegador ejecute el
                        // href
                        return false;
                    });
                });
                $("div#tablaresultados .pagelinks a").each(function() {
                    // Itera sobre cada enlace de paginación para sobreescribirlo
                    $(this).click(function() {
                        var link = $(this).attr("href") + " #tablaresultados";
                        $("div#tablaresultados").load(link, {}, es.displaytag.onPeopleTableLoad);
                        return false;
                    });
                });
            }
    };

    $(document).ready(function() {
        // Carga inicial cuando el DOM está preparado. Se asume que vas a inyectar
        // dentro de un div
        // con el id "tablaresultados" que existe en la página.
        $("div#tablaresultados").load("/path/remoto/que/genera/tabla/arriba", {}, es.displaytag.onPeopleTableLoad);
    });

</script>

Para evitar el problema 1 comentado en la solución con ajaxtags, añadimos a los enlaces el id “#tablaresultados” para indicarle que recargue únicamente la capa que contiene la tabla. El problema 2 no lo tenemos con esta solución puesto que no se sobreescribe el atributo href. En el caso de mostrar la tabla en un navegador que no interprete javascript la tabla funcionará normalmente recargando toda la página.

Conclusiones

Personalmente me inclino por la solución con jQuery, ya que en nuestro caso no es necesario modificar código en el servidor y porque necesitamos cumplir con criterios de accesibilidad.

Seguramente existen multitud de componentes o tablas con Ajax para incorporar a las aplicaciones web, pero si ya tienes una página con displaytag, el código en el servidor que la alimenta y deseas incorporar ajax y mantener lo que tenías, estas dos soluciones son unas muy buenas opciones.

Actualización

(25-03-2011) Posterior a esta entrada se desarrolló un plugin jQuery (displaytag-ajax) que provee la capacidad de refresco AJAX a tablas generadas mediante Display Tag. Debo dar las gracias a Yajairo87 ya que gracias a su comentario pude descargármelo y probarlo (funciona perfectamente). Si tenéis curiosidad y miráis el código comprobaréis que realiza el mismo proceso comentado en esta entrada. Podéis descargarlo en la siguiente dirección: http://joseantoniosaiz.es/samples/displaytag-ajax/displaytag-ajax-1.2_0.zip