Tareas programadas en Spring

Última actualización: 02/06/2018

logo spring

Es posible que al desarrollar una aplicación la mejor solución para ciertas funcionalidades sea la implementación de tareas programables, esto es, métodos que se ejecuten periódicamente en momentos determinados o bien cada cierto periodo de tiempo. Ejemplos: revisión de la base de datos para la generación de informes de seguimiento, envío de notificaciones, realizar tareas de sincronización con otros sistemas, etc. Asimismo, estas tareas suelen formar parte de la propia aplicación ya que así se pueden utilizar los servicios, capa DAO, etc, de la misma.

En Java la herramienta más empleada para la ejecución de tareas programadas es Quartz pero si utilizamos Spring y la casuística es sencilla podemos utilizar las funcionalidades proporcionadas de serie. En este tutorial echaremos un vistazo a estas funcionalidades en Spring 4 y que siguen disponibles y utilizables de la misma forma en Spring 5.

Proyecto para pruebas

Se va a utilizar una clase Main para realizar las pruebas con un bean de Spring que contiene métodos que se ejecutan de forma periódica. Puesto que todo lo necesario ya viene incluido en el “core” de Spring no se necesita ninguna dependencia adicional. El pom.xml completo es el siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.danielme.blog.spring</groupId>
    <artifactId>schedule</artifactId>
    <version>1.0</version>    
    <packaging>jar</packaging>
    
    <name>Spring - Scheduled tasks</name>
    <description>Spring - Scheduled tasks</description>
    <url>https://danielme.com/2014/05/12/tareas-programadas-en-spring</url>
    <inceptionYear>2014</inceptionYear>
    
    <scm>
    	<url>https://github.com/danielme-com/spring-scheduled-tasks</url>
    </scm>
    
    <licenses>
		<license>
			<name>GPL 3</name>
			<url>http://www.gnu.org/licenses/gpl-3.0.html</url>
		</license>
	</licenses>

    <properties>
		<java.version>1.6</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		
		<maven.compiler.plugin.version>3.1</maven.compiler.plugin.version>	
		<maven.eclipse.plugin.version>2.9</maven.eclipse.plugin.version>		
		<maven.shade.plugin.version>2.1</maven.shade.plugin.version>
		<maven.jar.plugin.version>2.4</maven.jar.plugin.version>
		<project.build.mainClass>com.danielme.blog.spring.scheduled.Main</project.build.mainClass>
		<maven.exec.plugin.version>1.2.1</maven.exec.plugin.version>
		
		<spring.version>4.0.4.RELEASE</spring.version>	
		<slf4j.log4j12.version>1.7.6</slf4j.log4j12.version>
		
	</properties>
    
    
	<build>
		<plugins>
		
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>${maven.compiler.plugin.version}</version>
				<configuration>
					<encoding>${project.build.sourceEncoding}</encoding>
					<source>${java.version}</source>
					<target>${java.version}</target>
					<compilerArgument>-proc:none</compilerArgument>
				</configuration>
			</plugin>

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-eclipse-plugin</artifactId>
				<version>${maven.eclipse.plugin.version}</version>
				<configuration>
					<projectNameTemplate>${project.name}</projectNameTemplate>
					<wtpmanifest>false</wtpmanifest>
					<wtpapplicationxml>false</wtpapplicationxml>
				</configuration>
			</plugin>
			
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-jar-plugin</artifactId>
				<version>${maven.jar.plugin.version}</version>
				<configuration>
					<archive>
						<manifest>
							<addClasspath>true</addClasspath>
							<mainClass>${project.build.mainClass}</mainClass>
						</manifest>
						<manifestEntries>
							<Implementation-Version>${project.version}</Implementation-Version>
							<Implementation-Title>${project.artifactId}</Implementation-Title>
							<Extension-Name>${project.artifactId}</Extension-Name>
							<Built-By>danielme.com</Built-By>
						</manifestEntries>
					</archive>
				</configuration>
			</plugin>

			<!-- mvn clean package exec:java -->
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>exec-maven-plugin</artifactId>
				<version>${maven.exec.plugin.version}</version>
				<executions>
					<execution>
						<goals>
							<goal>java</goal>
						</goals>
					</execution>
				</executions>
				<configuration>
					<mainClass>${project.build.mainClass}</mainClass>
				</configuration>
			</plugin>


			<!-- package all dependencies into an executable jar -->
			<!-- mvn clean package shade:shade -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-shade-plugin</artifactId>
				<version>${maven.shade.plugin.version}</version>
				<executions>
					<execution>
						<phase>package</phase>
						<goals>
							<goal>shade</goal>
						</goals>
					</execution>
				</executions>
				<configuration>
					<finalName>${project.artifactId}-${project.version}-full</finalName>
					<transformers>
						<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
							<mainClass>${project.build.mainClass}</mainClass>
						</transformer>
					</transformers>
						<!-- http://zhentao-li.blogspot.com.es/2012/06/maven-shade-plugin-invalid-signature.html -->
					<filters>
						<filter>
							<artifact>*:*</artifact>
							<excludes>
								<exclude>META-INF/*.SF</exclude>
								<exclude>META-INF/*.DSA</exclude>
								<exclude>META-INF/*.RSA</exclude>
							</excludes>
						</filter>
					</filters>
				</configuration>
			</plugin>

		</plugins>	
	</build>

    <dependencies>
	
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>

       <dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>${slf4j.log4j12.version}</version>
		</dependency>

    </dependencies>

   
</project>

Se puede generar un proyecto para Eclipse con el siguiente comando:

$mvn clean eclipse:eclipse

El proyecto completo presenta la siguiente estructura:

eclipse-project

Definiendo tareas mediante anotaciones

Primero veamos cómo programar tareas utilizando anotaciones. Para ello, vamos a habilitar las anotaciones tanto para los beans como para las tareas (<task:annotation-driven />).

<?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:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd">
	
	<context:annotation-config />	

	<context:component-scan base-package="com.danielme.blog" />
	    
	<task:annotation-driven />
		
</beans>

Si la configuración la realizamos programáticamente habilitaremos las tareas programadas con la anotación @EnableScheduling en una clase de configuración de Spring (es decir, una clase anotada con @Configuration). Hay un ejemplo de configuración de Spring en Java en mi tutorial Persistencia en BD con Spring.

Para programar la ejecución automática de un método de un bean se utiliza la anotación Scheduled (esta anotación es incompatible con @Transactional, así que si tenemos que ejecutar una lógica transaccional debemos extraerla a un método en otra clase). Los parámetros admitidos son los siguientes (comprobar en el javadoc su disponibilidad según la versión de Spring):

  • initialDelay: milisegundos desde la instanciación del bean hasta la primera ejecución programada del método.
  • fixedDelay: milisegundos tras el fin de la última ejecución del método que deben pasar hasta una nueva ejecución del método. Lo usaremos para métodos que se deban ejecutar con una frecuencia muy alta y que sean susceptibles de durar bastante.
  • fixedRate: el método se ejecutará según el periodo indicado en milisegundos, sin tenerse en cuenta si la última ejecución ya terminó.
  • cron: permite definir una regla cron estableciendo un periodo regular de ejecución de forma muy precisa, por ejemplo a cierta hora de un día de la semana. El formato de la expresión cron es el mismo que el admitido por Quartz y podemos apoyarnos en este generador online.

Para probar estas opciones crearemos un bean llamado ScheduledTasks. Los métodos programados simplemente imprimirán en el log su ejecución.

package com.danielme.blog.spring.scheduled;

import org.apache.log4j.Logger;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class ScheduledTasks 
{
	private static final Logger LOG = Logger.getLogger(ScheduledTasks.class);
	
	@Scheduled(initialDelay=30000, fixedDelay = 1500)
	public void task1()
	{
		LOG.info("task1");
	}
	
	@Scheduled(cron="*/10 * * * * ?")
	public void task2()
	{
		LOG.info("task2");
	}
	
}

Para que estos métodos se ejecuten lo más sencillo es crear un Main que arranque el contexto de Spring.

package com.danielme.blog.spring.scheduled;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main 
{

	public static void main(String[] args)
	{
		 new ClassPathXmlApplicationContext("/applicationContext.xml");
	}
}

Ejecutando el Main veremos en la salida estándar configurada en el log4j.xml (System.out) la traza con la ejecución de las tareas programadas. La tarea dos se ejecuta cada diez segundos y la uno cada segundo y medio a partir del segundo 30, tal y como se ha indicado y se puede comprobar en el log:

05-10-2014 09:33:30,001 PM  INFO ScheduledTasks:22 - task2
05-10-2014 09:33:40,000 PM  INFO ScheduledTasks:22 - task2
05-10-2014 09:33:50,000 PM  INFO ScheduledTasks:22 - task2
05-10-2014 09:33:55,133 PM  INFO ScheduledTasks:16 - task1
05-10-2014 09:33:56,633 PM  INFO ScheduledTasks:16 - task1
05-10-2014 09:33:58,134 PM  INFO ScheduledTasks:16 - task1
05-10-2014 09:33:59,635 PM  INFO ScheduledTasks:16 - task1
05-10-2014 09:34:00,000 PM  INFO ScheduledTasks:22 - task2

Personalmente considero una buena práctica definir los intervalos de tiempo fuera de las clases y en un mismo fichero para tener esta configuración centralizada y de fácil acceso y modificación. Esta estrategia permite modificar los intervalos sin tener que recompilar y empaquetar toda la aplicación. Para conseguir esto, voy a aplicar lo visto en el artículo Ficheros .properties en Spring IoC y crear el archivo /src/main/resources/tasks.properties.

task1.initialDelay=30000
task1.fixedDelay=1500
task2.cron=*/10 * * * * ?

Se importa este fichero en el contexto de Spring en el applicationContext:

<context:property-placeholder location="classpath:tasks.properties"/>

La clase quedará tal que así, obsérvese que ahora se utilizan los atributos initialDelayString y fixedDelayString.

package com.danielme.blog.spring.scheduled;

import org.apache.log4j.Logger;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class ScheduledTasks {

	private static final Logger LOG = Logger.getLogger(ScheduledTasks.class);
	
	@Scheduled(initialDelayString="${task1.initialDelay}", fixedDelayString = "${task1.fixedDelay}")
	public void task1()
	{
		LOG.info("task1");
	}
	
	@Scheduled(cron="${task2.cron}")
	public void task2()
	{
		LOG.info("task2");
	}
	
}

Configuración en XML

La definición de tareas programadas se puede realizar de forma tradicional directamente en XML sin utilizar anotaciones. El siguiente snippet de código permite replicar la última configuración que hemos visto:


  	<context:property-placeholder location="classpath:tasks.properties"/>
  	
  	<bean id="scheduledTasks" class="com.danielme.blog.spring.scheduled.ScheduledTasks" />
 
     <task:scheduled-tasks>
        <task:scheduled ref="scheduledTasks" method="task1" initial-delay="${task1.initialDelay}" fixed-delay="${task1.fixedDelay}"/>
        <task:scheduled ref="scheduledTasks" method="task2" cron="${task2.cron}"/>
    </task:scheduled-tasks>

Código de ejemplo

El proyecto completo se encuentra disponible en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.

2 Responses to Tareas programadas en Spring

  1. Mario says:

    Buen tutorial pero al probarlo me di cuenta que pusiste como String las properties que solo deberian estar sin las comillas. En caso contario anda solo en pocos casos donde no se tenga un numero fijo en los segundos o demas. Ej importacion.cron=0 10 0 * * MON-FRI no funciona en properties si le agrego las comillas.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

w

Conectando a %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

A %d blogueros les gusta esto: