Aller au contenu principal

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)
│ │ │ │ │ │
* * * * * *
ExpressionDescription
*Toutes les valeurs
*/15Tous les 15
0-23Plage de valeurs
1,15Liste de valeurs
?Non spécifié (jour)
LDernier (jour du mois)
WJour ouvrable le plus proche
#Nième jour de la semaine (2#1 = premier lundi)

2.5 Exemples Cron Courants​

ExpressionDescription
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 * * SUNTous les dimanches à minuit
0 0 0 1 * *Premier jour du mois
0 0 0 L * *Dernier jour du mois
0 0 0 ? * 6LDernier 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​

AnnotationUsage
@EnableSchedulingActive le scheduling
@EnableAsyncActive les méthodes async
@ScheduledMarque une méthode planifiée
@AsyncMarque une méthode asynchrone

Options @Scheduled​

OptionDescription
fixedRateIntervalle depuis le début
fixedDelayIntervalle depuis la fin
cronExpression Cron
initialDelayDélai initial
zoneTimezone pour Cron

Bonnes Pratiques​

  1. Toujours gérer les exceptions dans les tâches
  2. Utiliser des pools de threads dédiés
  3. Monitorer l'exécution des tâches
  4. Éviter les tâches trop longues
  5. Utiliser le locking en environnement cluster
  6. Logger le début et la fin des tâches

Chapitre Suivant: WebSockets

Retour au Module 14