Chapitre 4: Tâches Planifiées et Asynchrones
Objectifs du Chapitre
- Configurer les tâches planifiées avec @Scheduled
- Maîtriser les expressions Cron
- Implémenter des méthodes asynchrones avec @Async
- Configurer les pools de threads
- Gérer les erreurs et le monitoring
1. Configuration de Base
1.1 Activer le Scheduling
package com.example.scheduling;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableScheduling
@EnableAsync
public class SchedulingApplication {
public static void main(String[] args) {
SpringApplication.run(SchedulingApplication.class, args);
}
}
1.2 Configuration du Thread Pool
package com.example.scheduling.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
@Configuration
public class SchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setTaskScheduler(taskScheduler());
}
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("scheduled-");
scheduler.setAwaitTerminationSeconds(60);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setErrorHandler(t -> {
// Log des erreurs
System.err.println("Scheduled task error: " + t.getMessage());
});
return scheduler;
}
}
2. @Scheduled - Tâches Planifiées
2.1 Fixed Rate
Exécute à intervalle fixe depuis le début de la dernière exécution.
@Component
public class ScheduledTasks {
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
// Toutes les 5 secondes
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
log.info("Current time: {}", LocalDateTime.now());
}
// Avec délai initial de 10 secondes
@Scheduled(fixedRate = 5000, initialDelay = 10000)
public void taskWithInitialDelay() {
log.info("Task with initial delay");
}
// Utiliser les propriétés
@Scheduled(fixedRateString = "${task.fixed-rate:5000}")
public void configurableTask() {
log.info("Configurable task");
}
}
2.2 Fixed Delay
Exécute à intervalle fixe depuis la fin de la dernière exécution.
@Component
public class ScheduledTasks {
// 3 secondes après la fin de l'exécution précédente
@Scheduled(fixedDelay = 3000)
public void processQueue() {
log.info("Processing queue...");
// Simulation de traitement
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
log.info("Queue processed");
}
}
2.3 Expressions Cron
@Component
public class ScheduledTasks {
// Tous les jours à minuit
@Scheduled(cron = "0 0 0 * * *")
public void dailyCleanup() {
log.info("Running daily cleanup");
}
// Toutes les heures, à la minute 0
@Scheduled(cron = "0 0 * * * *")
public void hourlyReport() {
log.info("Generating hourly report");
}
// Tous les lundis à 9h
@Scheduled(cron = "0 0 9 * * MON")
public void weeklyReport() {
log.info("Generating weekly report");
}
// Le premier jour de chaque mois à 6h
@Scheduled(cron = "0 0 6 1 * *")
public void monthlyReport() {
log.info("Generating monthly report");
}
// Toutes les 15 minutes pendant les heures de bureau
@Scheduled(cron = "0 */15 9-17 * * MON-FRI")
public void businessHoursTask() {
log.info("Business hours task");
}
// Avec timezone
@Scheduled(cron = "0 0 9 * * *", zone = "America/Montreal")
public void taskWithTimezone() {
log.info("Task at 9 AM Montreal time");
}
}
2.4 Syntaxe Cron
┌───────────── seconde (0-59)
│ ┌───────────── minute (0-59)
│ │ ┌───────────── heure (0-23)
│ │ │ ┌───────────── jour du mois (1-31)
│ │ │ │ ┌───────────── mois (1-12 ou JAN-DEC)
│ │ │ │ │ ┌───────────── jour de la semaine (0-7 ou SUN-SAT)
│ │ │ │ │ │
* * * * * *
| Expression | Description |
|---|---|
* | Toutes les valeurs |
*/15 | Tous les 15 |
0-23 | Plage de valeurs |
1,15 | Liste de valeurs |
? | Non spécifié (jour) |
L | Dernier (jour du mois) |
W | Jour ouvrable le plus proche |
# | Nième jour de la semaine (2#1 = premier lundi) |
2.5 Exemples Cron Courants
| Expression | Description |
|---|---|
0 0 * * * * | Toutes les heures |
0 */10 * * * * | Toutes les 10 minutes |
0 0 8-18 * * * | Toutes les heures de 8h à 18h |
0 0 0 * * * | Tous les jours à minuit |
0 0 0 * * SUN | Tous les dimanches à minuit |
0 0 0 1 * * | Premier jour du mois |
0 0 0 L * * | Dernier jour du mois |
0 0 0 ? * 6L | Dernier vendredi du mois |
3. @Async - Méthodes Asynchrones
3.1 Configuration
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
System.err.println("Async error in method " + method.getName() + ": " + ex.getMessage());
};
}
}
3.2 Méthodes Asynchrones
@Service
public class EmailService {
private static final Logger log = LoggerFactory.getLogger(EmailService.class);
// Fire and forget
@Async
public void sendEmailAsync(String to, String subject, String body) {
log.info("Sending email to {} on thread {}", to, Thread.currentThread().getName());
// Simulation d'envoi
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
log.info("Email sent to {}", to);
}
// Avec retour de valeur
@Async
public CompletableFuture<Boolean> sendEmailWithResult(String to, String subject) {
log.info("Sending email to {}", to);
try {
// Simulation
Thread.sleep(2000);
log.info("Email sent successfully to {}", to);
return CompletableFuture.completedFuture(true);
} catch (Exception e) {
log.error("Failed to send email to {}", to, e);
return CompletableFuture.completedFuture(false);
}
}
// Avec executor spécifique
@Async("emailExecutor")
public void sendWithSpecificExecutor(String to) {
// Utilise le bean "emailExecutor"
}
}
3.3 Utilisation
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
private final EmailService emailService;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
// Créer la commande (synchrone)
Order order = orderService.createOrder(request);
// Envoyer l'email (asynchrone - ne bloque pas)
emailService.sendEmailAsync(
request.getEmail(),
"Order Confirmation",
"Your order #" + order.getId() + " has been created"
);
// Retourne immédiatement
return ResponseEntity.ok(order);
}
@PostMapping("/bulk-email")
public ResponseEntity<String> sendBulkEmails(@RequestBody List<String> emails) {
// Envoyer plusieurs emails en parallèle
List<CompletableFuture<Boolean>> futures = emails.stream()
.map(email -> emailService.sendEmailWithResult(email, "Newsletter"))
.collect(Collectors.toList());
// Attendre tous les résultats
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> {
long successCount = futures.stream()
.map(CompletableFuture::join)
.filter(Boolean::booleanValue)
.count();
log.info("Sent {} emails successfully", successCount);
});
return ResponseEntity.accepted().body("Emails are being sent");
}
}
4. Cas d'Usage Courants
4.1 Nettoyage de Données Expirées
@Component
public class CleanupTasks {
private final SessionRepository sessionRepository;
private final TempFileService tempFileService;
// Nettoyer les sessions expirées toutes les heures
@Scheduled(cron = "0 0 * * * *")
public void cleanupExpiredSessions() {
log.info("Starting session cleanup");
LocalDateTime expirationThreshold = LocalDateTime.now().minusHours(24);
int deleted = sessionRepository.deleteByLastAccessBefore(expirationThreshold);
log.info("Cleaned up {} expired sessions", deleted);
}
// Nettoyer les fichiers temporaires tous les jours à 3h
@Scheduled(cron = "0 0 3 * * *")
public void cleanupTempFiles() {
log.info("Starting temp files cleanup");
int deleted = tempFileService.deleteOlderThan(Duration.ofDays(7));
log.info("Deleted {} temp files", deleted);
}
}
4.2 Génération de Rapports
@Component
public class ReportTasks {
private final ReportService reportService;
private final EmailService emailService;
// Rapport quotidien à 6h
@Scheduled(cron = "0 0 6 * * *")
public void generateDailyReport() {
log.info("Generating daily report");
DailyReport report = reportService.generateDailyReport(LocalDate.now().minusDays(1));
emailService.sendReportAsync("admin@company.com", report);
log.info("Daily report generated and sent");
}
// Rapport hebdomadaire le lundi à 7h
@Scheduled(cron = "0 0 7 * * MON")
public void generateWeeklyReport() {
log.info("Generating weekly report");
WeeklyReport report = reportService.generateWeeklyReport();
emailService.sendReportAsync("management@company.com", report);
}
}
4.3 Synchronisation de Données
@Component
public class SyncTasks {
private final ExternalApiService externalApi;
private final ProductRepository productRepository;
// Synchroniser les prix toutes les 15 minutes
@Scheduled(fixedRate = 900000) // 15 minutes
public void syncPrices() {
log.info("Starting price sync");
try {
List<PriceUpdate> updates = externalApi.fetchPriceUpdates();
updates.forEach(update -> {
productRepository.updatePrice(update.getProductId(), update.getNewPrice());
});
log.info("Synced {} price updates", updates.size());
} catch (Exception e) {
log.error("Price sync failed", e);
}
}
}
4.4 Health Check
@Component
public class HealthCheckTasks {
private final List<ExternalService> externalServices;
private final AlertService alertService;
// Vérifier les services externes toutes les minutes
@Scheduled(fixedRate = 60000)
public void checkExternalServices() {
externalServices.forEach(service -> {
boolean healthy = service.healthCheck();
if (!healthy) {
log.warn("Service {} is unhealthy", service.getName());
alertService.sendAlert("Service " + service.getName() + " is down");
}
});
}
}
5. Gestion des Erreurs
5.1 Try-Catch dans les Tâches
@Component
public class RobustScheduledTask {
@Scheduled(fixedRate = 60000)
public void robustTask() {
try {
performTask();
} catch (Exception e) {
log.error("Task failed, will retry on next schedule", e);
// Ne pas re-throw pour éviter d'arrêter le scheduling
}
}
}
5.2 Error Handler Global
@Configuration
public class SchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setErrorHandler(t -> {
log.error("Scheduled task error", t);
// Envoyer alerte, métriques, etc.
alertService.sendAlert("Scheduled task failed: " + t.getMessage());
});
scheduler.initialize();
taskRegistrar.setTaskScheduler(scheduler);
}
}
6. Contrôle Conditionnel
6.1 Activer/Désactiver par Configuration
@Component
@ConditionalOnProperty(name = "app.scheduling.enabled", havingValue = "true")
public class ScheduledTasks {
// Ces tâches ne s'exécutent que si app.scheduling.enabled=true
}
6.2 Contrôle par Profil
@Component
@Profile("!test") // Pas en environnement de test
public class ProductionScheduledTasks {
@Scheduled(cron = "0 0 0 * * *")
public void productionOnlyTask() {
// S'exécute seulement en production
}
}
6.3 Leader Election (Cluster)
@Component
public class ClusterAwareTask {
private final LockProvider lockProvider;
@Scheduled(cron = "0 0 * * * *")
@SchedulerLock(name = "hourlyTask", lockAtLeastFor = "PT5M", lockAtMostFor = "PT30M")
public void hourlyTask() {
// Une seule instance dans le cluster exécute cette tâche
log.info("Running hourly task on this instance");
}
}
7. Résumé
Annotations
| Annotation | Usage |
|---|---|
@EnableScheduling | Active le scheduling |
@EnableAsync | Active les méthodes async |
@Scheduled | Marque une méthode planifiée |
@Async | Marque une méthode asynchrone |
Options @Scheduled
| Option | Description |
|---|---|
fixedRate | Intervalle depuis le début |
fixedDelay | Intervalle depuis la fin |
cron | Expression Cron |
initialDelay | Délai initial |
zone | Timezone pour Cron |
Bonnes Pratiques
- Toujours gérer les exceptions dans les tâches
- Utiliser des pools de threads dédiés
- Monitorer l'exécution des tâches
- Éviter les tâches trop longues
- Utiliser le locking en environnement cluster
- Logger le début et la fin des tâches