Programación de tareas de Spring Boot

Programar tareas o repetirlas en intervalos suele ser una característica útil. En este artículo, exploraremos las capacidades de programación de Spring Boot.

Introducción

La programación de tareas para que se realicen en una fecha posterior o se repitan en un intervalo fijo es una función muy útil. Por ejemplo, los sistemas de boletines o las tareas que procesan información en un marco de tiempo establecido dependen de que se programen para ejecutarse en ciertos puntos de tiempo.

Dado que Spring Boot ofrece varias opciones, las cubriremos e implementaremos todas.

Configuración del proyecto

Como de costumbre, cuando se trata de crear aplicaciones Spring Boot, hacer un proyecto básico es más fácil con la ayuda de Spring Initializr. No necesita ninguna dependencia adicional para habilitar la programación.

Para habilitar la programación, todo lo que tenemos que hacer es anotar nuestra clase principal:

1
2
3
4
5
6
7
@SpringBootApplication
@EnableScheduling
public class SampleScheduleApplication {
    public static void main(String[] args) {
        SpringApplication.run(SampleScheduleApplication.class, args);
    }
}

La anotación @EnableScheduling permite que el contenedor Spring detecte cualquier anotación @Scheduled en los beans administrados por Spring.

Programación condicional

Hay otra forma de habilitar la programación: mediante el uso de la anotación @ConditionalOnProperty. Nos permite "activar" y "desactivar" nuestras clases de configuración configurando una propiedad en la clase application.properties.

Para hacer esto, hagamos una nueva clase y anótela con las anotaciones @EnableScheduling, @Configuration y @ConditionalOnProperty:

1
2
3
4
@EnableScheduling
@Configuration
@ConditionalOnProperty(name = "spring.enable.scheduling")
public class ScheduleEnabling {}

Ahora, en el archivo application.properties, agreguemos nuestra nueva propiedad y configúrela en true:

1
spring.enable.scheduling = true

Con solo cambiar esta variable, podemos activar y desactivar la funcionalidad.

Para ejecutar un método en un programa, debe anotarlo con la anotación @Scheduled. Cambiar los parámetros de la anotación definirá si es a una tasa fija, con un retraso fijo, intervalos personalizados, etc.

Programación con tasa fija y cadena de tasa fija

Para programar un método para que se ejecute a una tasa fija, agregaremos el parámetro adecuado a nuestra anotación - @Scheduled(fixedRate). Este parámetro acepta números enteros, expresados ​​en milisegundos. Entonces, si desea una tasa de 1 segundo, la tasa debe ingresarse como 1000 ya que el valor es milisegundos.

Alternativamente, puede usar el parámetro @Scheduled(fixedRateString) para externalizar la cantidad usando una variable de cadena de entorno.

Para evitar confusiones, usaremos fixedRateString, que es esencialmente un parámetro de cadena que especifica la tasa, en lugar del valor entero. Puede ser un poco complicado tratar de hacer que un método se repita mensualmente en milisegundos.

Establezcamos una variable en el archivo application.properties:

1
sample.schedule.string = PT2S

El prefijo PT es el estándar ISO-8601 para anotar duraciones o períodos, y con esto podemos llamar a sample.schedule.string para programar una llamada de método en 2 segundos.

Esto también nos permite especificar diferentes retrasos para diferentes perfiles en uso:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Scheduled(fixedRateString = "${sample.schedule.string}")
public void scheduleTaskWithFixedRate() throws InterruptedException {
    task1();
    task2();
}

public void task1() throws InterruptedException {
    logger.info("Task 1 starts" + Thread.currentThread());
    Thread.sleep(1000);
    logger.info("Task 1 ends" + Thread.currentThread());
}

public void task2() throws InterruptedException {
    logger.info("Task 2 starts" + Thread.currentThread());
    Thread.sleep(1000);
    logger.info("Task 2 ends" + Thread.currentThread());
}

Vamos a ejecutar este fragmento de código:

Scheduling with Fixed Rate

Sin embargo, podemos ver un problema aquí:

La tarea 1 comienza a las 00:01:44 y finaliza a las 00:01:45, como se esperaba.
La tarea 2 comienza a las 00:01:45 y finaliza a las 00:01:46, como se esperaba.
La tarea 1 comienza a las 00:01:46 y finaliza a las 00:01:47.

Dado que tanto task1() como task2() han sido ejecutados, esperaría que el método esperara 2 segundos adicionales para volver a ejecutarse.

Las tareas de tasa fija no esperan a que se complete la ejecución anterior, simplemente invocan el método a una tasa específica. Como se tarda 2 segundos en finalizar los métodos task1() y task2(), el método se invoca de nuevo al mismo tiempo que estos dos finalizan.

Esto puede convertirse en un problema en un entorno con múltiples tareas programadas.

Considere esto: task1() tarda 1 minuto en completarse, y task2() tarda 5 segundos en completarse. Dado que ambos se ejecutan en un solo subproceso, puede haber una situación en la que task1() comience a procesarse y bloquee el subproceso. Esto no permitirá que task2() se procese incluso si hay un fixedRate de 5 segundos.

En este escenario, necesitamos aumentar la cantidad de subprocesos que están disponibles en nuestro grupo de subprocesos para la programación. Spring proporciona una propiedad que podemos manipular para especificar el tamaño: spring.task.scheduling.pool.size - el valor predeterminado es 1.

Podemos usar una tarifa fija cuando una tarea en particular debe realizarse repetidamente, pero cada tarea es independiente de la otra. Además, tenga cuidado de no tener tareas pesadas sin una tasa adecuada, ya que no completarlas puede generar un desagradable OutOfMemoryError.

Programación con fixedDelay y fixedDelayString

Un fixedDelay funciona de manera muy similar a un fixedRate. Pero la diferencia aquí es que el retraso fijo espera hasta la finalización de la ejecución anterior para iniciar la siguiente. Imagine un escenario en el que su función tarda 1 segundo en completar la ejecución y le ha dado un retraso fijo de 2 segundos.

Esto, a su vez, dará como resultado un total de 3 segundos.

En los registros a continuación, puede ver claramente que la diferencia entre las dos tareas posteriores es de 3 segundos. Esto incluye el tiempo de retraso fijo de 1 segundo, así como los 2 segundos que le hemos dado como suspensión:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Scheduled(fixedDelayString = "${sample.schedule.string}")
public void scheduleTaskWithFixedDelay() throws InterruptedException {
    task1();
}

public void task1() throws InterruptedException {
    logger.info("Task 1 starts" + Thread.currentThread());
    Thread.sleep(1000);
    logger.info("Task 1 ends" + Thread.currentThread());
}

Vamos a ejecutar este fragmento de código:

Scheduling with Fixed Delay

Hay un parámetro adicional que se puede agregar a las tareas programadas, y es initialDelay.

Este no requiere mucha explicación ya que se usa junto con los dos anteriores. El retraso inicial, como sugiere su nombre, proporciona el retraso inicial para la primera ejecución.

Si tiene una función con un retraso inicial de 2 segundos y una velocidad fija de 1 segundo, la primera ejecución se retrasará 2 segundos y la función se ejecutará cada 1 segundo después:

1
2
3
4
@Scheduled(initialDelay = 1000, fixedRateString = "${sample.schedule.string}")
public void scheduleTaskWithInitialDelay() throws InterruptedException {
    task1();
}

También podemos optar por utilizar un initialDelayString que nos permita externalizar el valor del retraso.

Intervalos de tiempo personalizados {#intervalos de tiempo personalizados}

La tasa fija y el retraso fijo son los parámetros más utilizados para la programación y las cadenas de retraso nos permiten externalizar los valores y hacerlos configurables.

Pero hasta ahora solo hemos visto ejemplos muy genéricos de las tarifas. Puede haber una situación en la que necesitemos intervalos de tiempo muy específicos. He aquí, expresiones cron personalizadas.

Expresiones cron

La mayoría de los desarrolladores probablemente han oído hablar de la utilidad cron en Linux. Es un proceso daemon que se ejecuta sin necesidad de intervención del usuario y ejecuta tareas.

La sintaxis de las expresiones cron en la utilidad cron y la sintaxis de las expresiones cron para la programación son en su mayoría similares.

Las expresiones cron son básicamente cadenas que describen los detalles de la programación. Proporciona mucho más control que los 2 métodos anteriores:


Nombre Obligatorio Valores permitidos Caracteres especiales permitidos Segundos Sí 0-59 , - * / Minutos Sí 0-59 , - * / Horas Sí 0-23 , - * / Día del mes Sí 1-31 , - * / L W C Mes Sí 0-11 o ENE-DIC , - * / Día de la semana Sí 1-7 o DOM-SÁB , - * / L C # Año No vacío o 1970-2099 , - * /


La tabla anterior especifica los valores requeridos, los valores permitidos y los caracteres especiales para una expresión cron.

Las expresiones cron pueden ser muy simples, pero también muy complejas. Una comprensión clara de los valores hará que sea más fácil jugar con ellos.

Excepto el campo del año, todos los demás campos son obligatorios:

1
<second> <minute> <hour> <day-of-month> <month> <day-of-week> <year>

Ejemplo: 0 0 12 * * ? 2019: esta expresión cron se activa a las 12 p. m., todos los días del mes, para todos los meses, durante el año 2019.

Para algunos valores comunes, también puede usar las anotaciones predefinidas:

  • @reboot: Programe el método para cada reinicio de la aplicación
  • @anualmente/@anualmente: ​​programe el método para que se ejecute una vez al año
  • @monthly: programe el método para que se ejecute una vez al mes
  • @weekly: programe el método para que se ejecute una vez por semana
  • @daily/@midnight: programe el método para que se ejecute una vez al día
  • @hourly: programe el método para que se ejecute una vez cada hora

Escribamos un ejemplo de código para esto:

1
2
3
4
@Scheduled(cron="0 0 12 * * ? 2019")
public void doSomething() {
    // Something
}

Una cosa importante a tener en cuenta al programar son las zonas horarias, la pesadilla de todos los desarrolladores que trabajan con el tiempo.

Es probable que desee establecer la bandera zona en una región específica. Por ejemplo, ejecutaremos este método a las 12 p. m., todos los días en 2019, según la zona horaria de París:

1
2
3
4
@Scheduled(cron="0 0 12 * * ? 2019", zone="Europe/Paris")
public void doSomething() {
    // Something
}

Puede encontrar todas las zonas horarias en los documentos oficiales de Oracle.

Por supuesto, también puede externalizar expresiones cron a través del archivo application.properties:

1
cron.expression= 0 0 12 * * ? 2019

Y luego invocarlo a través de:

1
2
3
4
@Scheduled(cron="${cron.expression}", zone="Europe/Paris")
public void doSomething() {
    // Something
}

También puede usar un sitio como Formateador libre para generar una expresión cron configurando los parámetros de entrada. Esto es muy útil para aquellos nuevos en la creación de expresiones cron.

Conclusión

En este artículo, hemos visto cómo podemos programar tareas con Spring Boot. La mayor ventaja de usar Spring Boot es la facilidad con la que podemos implementar la programación. No solo eso, también ofrece varias opciones para que podamos elegir la que se adapte a nuestras necesidades.

Los programadores son componentes esenciales de la mayoría de las aplicaciones porque envían información crítica y específica del usuario cuando es necesario. ¡Ahora ya sabes cómo!