Contactez-nous

Planification de tâches avec `@Scheduled` (`@EnableScheduling`)

Apprenez à exécuter des tâches périodiquement ou à des moments précis dans Spring Boot en utilisant l'annotation @Scheduled et la configuration @EnableScheduling.

Introduction à la planification de tâches dans Spring

De nombreuses applications nécessitent d'exécuter des tâches de manière répétée ou à des moments spécifiques, sans intervention manuelle directe. Ces tâches planifiées (scheduled tasks) peuvent inclure des opérations de maintenance, la génération de rapports, le nettoyage de données temporaires, l'envoi de notifications périodiques, l'exécution de traitements par lots (batch jobs), etc.

Spring Framework fournit un mécanisme puissant et simple pour gérer ces tâches planifiées directement au sein de l'application, via son abstraction de planification de tâches (Task Scheduling). Spring Boot simplifie encore davantage la configuration et l'utilisation de cette fonctionnalité grâce aux annotations `@EnableScheduling` et `@Scheduled`.

Cette approche est idéale pour des tâches simples à modérément complexes qui doivent s'exécuter au sein d'une instance d'application. Elle évite d'avoir recours à des outils de planification externes (comme `cron` au niveau de l'OS) pour des tâches intégrées à la logique applicative.

Activation de la planification : `@EnableScheduling`

Pour utiliser les fonctionnalités de planification de Spring, vous devez d'abord les activer explicitement. Cela se fait en ajoutant l'annotation `@EnableScheduling` à l'une de vos classes de configuration Spring (souvent la classe principale annotée avec `@SpringBootApplication`).

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling // Active la détection et l'exécution des tâches @Scheduled
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}

Lorsque Spring Boot détecte `@EnableScheduling`, il enregistre automatiquement un post-processeur qui recherche les beans contenant des méthodes annotées avec `@Scheduled` et configure l'infrastructure nécessaire pour les exécuter selon leur planification (par défaut, via un pool de threads dédié).

Définir une tâche planifiée : l'annotation `@Scheduled`

Une fois la planification activée, vous pouvez marquer n'importe quelle méthode d'un bean Spring (par exemple, un `@Component` ou un `@Service`) avec l'annotation `@Scheduled` pour indiquer qu'elle doit être exécutée selon un calendrier défini.

La méthode annotée avec `@Scheduled` doit respecter certaines contraintes :

  • Elle doit avoir un type de retour `void`.
  • Elle ne doit typiquement accepter aucun argument (bien que des cas d'usage avancés avec injection soient possibles, ce n'est pas courant).

La configuration de la planification se fait via les attributs de l'annotation `@Scheduled`.

Options de planification avec `@Scheduled`

L'annotation `@Scheduled` offre plusieurs façons de définir quand une tâche doit s'exécuter :

1. `fixedRate` : Exécution à intervalle fixe

  • Description : Exécute la tâche à un intervalle de temps fixe, mesuré entre le début de chaque exécution. Si une exécution prend plus de temps que l'intervalle défini, la suivante démarrera immédiatement après la fin de la précédente (pas de parallélisme par défaut sur la même tâche).
  • Syntaxe : `fixedRate = ` ou `fixedRateString = ""` (peut utiliser des placeholders `${...}`).
  • Exemple : Exécuter toutes les 5 secondes (5000 ms).
@Component
public class FixedRateScheduler {
    @Scheduled(fixedRate = 5000) // Exécute toutes les 5000 ms
    public void reportCurrentTime() {
        System.out.println("Fixed Rate Task :: Execution Time - " + System.currentTimeMillis() / 1000);
        // Simule une tâche qui prend du temps
        try { Thread.sleep(1000); } catch (InterruptedException e) {} 
    }
}

2. `fixedDelay` : Exécution avec délai fixe

  • Description : Exécute la tâche avec un délai fixe entre la fin de l'exécution précédente et le début de la suivante. Garantit qu'il y a toujours une pause entre les exécutions.
  • Syntaxe : `fixedDelay = ` ou `fixedDelayString = ""`.
  • Exemple : Attendre 3 secondes après la fin de chaque exécution avant de redémarrer.
@Component
public class FixedDelayScheduler {
    @Scheduled(fixedDelay = 3000) // Attend 3000 ms après la fin de la tâche
    public void processQueue() {
        System.out.println("Fixed Delay Task :: Start Processing - " + System.currentTimeMillis() / 1000);
        // Simule un traitement
        try { Thread.sleep(2000); } catch (InterruptedException e) {} 
        System.out.println("Fixed Delay Task :: End Processing - " + System.currentTimeMillis() / 1000);
    }
}

3. `cron` : Exécution basée sur une expression Cron

  • Description : Permet une planification beaucoup plus fine basée sur des expressions de type Cron, similaires à celles utilisées sous Unix. Idéal pour exécuter des tâches à des moments précis (ex: tous les jours à 2h du matin, le premier lundi de chaque mois).
  • Syntaxe : `cron = ""`.
  • Format Cron (Spring) : `second minute hour day-of-month month day-of-week`.
    • `second`: 0-59
    • `minute`: 0-59
    • `hour`: 0-23
    • `day-of-month`: 1-31
    • `month`: 1-12 ou JAN-DEC
    • `day-of-week`: 0-7 ou SUN-SAT (0 et 7 représentent Dimanche)
    • Caractères spéciaux : `*` (tous), `?` (non spécifié, pour jour/mois), `-` (intervalle), `,` (liste), `/` (incrément), `L` (dernier), `W` (jour ouvré), `#` (nième jour du mois).
  • Exemple : Exécuter tous les jours à 10h15 du matin.
@Component
public class CronScheduler {
    // Secondes, Minutes, Heures, Jour du mois, Mois, Jour de la semaine
    @Scheduled(cron = "0 15 10 * * *") // Tous les jours à 10:15:00
    public void generateDailyReport() {
        System.out.println("Cron Task :: Generating daily report - " + System.currentTimeMillis() / 1000);
    }

    // Exemple : toutes les 2 minutes
    @Scheduled(cron = "0 */2 * * * *") 
    public void runEveryTwoMinutes() {
        System.out.println("Cron Task :: Running every two minutes - " + System.currentTimeMillis() / 1000);
    }
}

4. `initialDelay` : Délai initial

  • Description : Peut être utilisé avec `fixedRate` et `fixedDelay` pour spécifier un délai (en millisecondes) avant la toute première exécution de la tâche après le démarrage de l'application.
  • Syntaxe : `initialDelay = ` ou `initialDelayString = ""`.
@Scheduled(fixedRate = 60000, initialDelay = 5000) // Exécute toutes les 60s, après un délai initial de 5s
public void delayedTask() { /* ... */ }

5. `zone` : Fuseau horaire pour Cron

  • Description : Permet de spécifier le fuseau horaire pour interpréter l'expression `cron`. Par défaut, utilise le fuseau horaire du serveur.
  • Syntaxe : `zone = ""` (ex: "Europe/Paris", "UTC").
@Scheduled(cron = "0 0 2 * * MON-FRI", zone = "America/New_York") // 2h du matin, du Lundi au Vendredi, heure de New York
public void runInNyTimezone() { /* ... */ }

Configuration et bonnes pratiques

Externalisation de la configuration : Il est recommandé d'externaliser les valeurs de `fixedRate`, `fixedDelay` ou `cron` dans vos fichiers `application.properties`/`yml` pour plus de flexibilité :

# application.properties
myapp.scheduling.report.cron=0 15 10 * * *
myapp.scheduling.heartbeat.rate=30000
@Scheduled(cron = "${myapp.scheduling.report.cron}")
public void generateDailyReport() { ... }

@Scheduled(fixedRateString = "${myapp.scheduling.heartbeat.rate}")
public void sendHeartbeat() { ... }

Pool de threads : Par défaut, Spring utilise un pool de threads avec une seule unité d'exécution (`single thread pool`) pour toutes les tâches `@Scheduled`. Si vous avez plusieurs tâches ou des tâches longues, elles peuvent se bloquer mutuellement. Vous pouvez configurer un pool de threads plus grand via les propriétés `spring.task.scheduling.pool.size` ou en définissant votre propre `TaskScheduler` bean.

Gestion des erreurs : Si une méthode `@Scheduled` lève une exception non interceptée, par défaut, l'exception est journalisée, mais les exécutions futures de cette tâche spécifique sont supprimées. Vous devriez généralement inclure un bloc `try-catch` dans vos méthodes `@Scheduled` pour gérer les erreurs de manière appropriée et éviter que la planification ne s'arrête.

Environnements distribués : Attention, si vous déployez plusieurs instances de votre application Spring Boot, chaque instance exécutera les tâches `@Scheduled` indépendamment. Si une tâche ne doit s'exécuter qu'une seule fois pour l'ensemble du système (par exemple, une tâche de clôture mensuelle), `@Scheduled` n'est pas la bonne solution. Il faut alors se tourner vers des solutions de tâches distribuées comme Spring Batch avec une base de données partagée, Quartz avec clustering, ou des mécanismes de verrouillage distribué.

Idempotence : Essayez de rendre vos tâches idempotentes autant que possible, c'est-à-dire qu'exécuter la tâche plusieurs fois avec les mêmes conditions produit le même résultat final. Cela aide en cas d'échec ou de redémarrage.

Conclusion

L'utilisation de `@EnableScheduling` et `@Scheduled` dans Spring Boot offre un moyen très pratique et puissant d'implémenter des tâches planifiées directement au sein de votre application. Que ce soit pour des exécutions à intervalle régulier (`fixedRate`, `fixedDelay`) ou à des moments précis (`cron`), ces annotations fournissent une solution simple et intégrée pour automatiser des opérations périodiques.

Tout en étant très efficace pour de nombreux cas d'usage, il est important de comprendre ses limitations, notamment en ce qui concerne la gestion des erreurs par défaut et son comportement dans des environnements distribués, afin de choisir l'outil de planification le mieux adapté à chaque situation.