Capítulo 4: Tarefas Agendadas e Assíncronas
Objetivos do Capítulo
- Configurar tarefas agendadas com @Scheduled
- Dominar expressões Cron
- Implementar métodos assíncronos com @Async
- Configurar pools de threads
- Gerenciar erros e monitoramento
1. Configuração Básica
1.1 Ativar o 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 Configuração do 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 de erros
System.err.println("Scheduled task error: " + t.getMessage());
});
return scheduler;
}
}
2. @Scheduled - Tarefas Agendadas
2.1 Fixed Rate
Executa em intervalo fixo a partir do início da última execução.
@Component
public class ScheduledTasks {
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
// A cada 5 segundos
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
log.info("Current time: {}", LocalDateTime.now());
}
// Com delay inicial de 10 segundos
@Scheduled(fixedRate = 5000, initialDelay = 10000)
public void taskWithInitialDelay() {
log.info("Task with initial delay");
}
// Usando propriedades
@Scheduled(fixedRateString = "${task.fixed-rate:5000}")
public void configurableTask() {
log.info("Configurable task");
}
}
2.2 Fixed Delay
Executa em intervalo fixo a partir do fim da última execução.
@Component
public class ScheduledTasks {
// 3 segundos após o fim da execução anterior
@Scheduled(fixedDelay = 3000)
public void processQueue() {
log.info("Processing queue...");
// Simulação de processamento
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
log.info("Queue processed");
}
}
2.3 Expressões Cron
@Component
public class ScheduledTasks {
// Todos os dias à meia-noite
@Scheduled(cron = "0 0 0 * * *")
public void dailyCleanup() {
log.info("Running daily cleanup");
}
// A cada hora, no minuto 0
@Scheduled(cron = "0 0 * * * *")
public void hourlyReport() {
log.info("Generating hourly report");
}
// Todas as segundas-feiras às 9h
@Scheduled(cron = "0 0 9 * * MON")
public void weeklyReport() {
log.info("Generating weekly report");
}
// No primeiro dia de cada mês às 6h
@Scheduled(cron = "0 0 6 1 * *")
public void monthlyReport() {
log.info("Generating monthly report");
}
// A cada 15 minutos durante o horário comercial
@Scheduled(cron = "0 */15 9-17 * * MON-FRI")
public void businessHoursTask() {
log.info("Business hours task");
}
// Com timezone
@Scheduled(cron = "0 0 9 * * *", zone = "America/Montreal")
public void taskWithTimezone() {
log.info("Task at 9 AM Montreal time");
}
}
2.4 Sintaxe Cron
┌───────────── segundo (0-59)
│ ┌───────────── minuto (0-59)
│ │ ┌───────────── hora (0-23)
│ │ │ ┌───────────── dia do mês (1-31)
│ │ │ │ ┌───────────── mês (1-12 ou JAN-DEC)
│ │ │ │ │ ┌───────────── dia da semana (0-7 ou SUN-SAT)
│ │ │ │ │ │
* * * * * *
| Expressão | Descrição |
|---|---|
* | Todos os valores |
*/15 | De 15 em 15 |
0-23 | Intervalo de valores |
1,15 | Lista de valores |
? | Não especificado (dia) |
L | Último (dia do mês) |
W | Dia útil mais próximo |
# | N-ésimo dia da semana (2#1 = primeira segunda-feira) |
2.5 Exemplos Cron Comuns
| Expressão | Descrição |
|---|---|
0 0 * * * * | A cada hora |
0 */10 * * * * | A cada 10 minutos |
0 0 8-18 * * * | A cada hora das 8h às 18h |
0 0 0 * * * | Todos os dias à meia-noite |
0 0 0 * * SUN | Todos os domingos à meia-noite |
0 0 0 1 * * | Primeiro dia do mês |
0 0 0 L * * | Último dia do mês |
0 0 0 ? * 6L | Última sexta-feira do mês |
3. @Async - Métodos Assíncronos
3.1 Configuração
@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étodos Assíncronos
@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());
// Simulação de envio
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
log.info("Email sent to {}", to);
}
// Com retorno de valor
@Async
public CompletableFuture<Boolean> sendEmailWithResult(String to, String subject) {
log.info("Sending email to {}", to);
try {
// Simulação
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);
}
}
// Com executor específico
@Async("emailExecutor")
public void sendWithSpecificExecutor(String to) {
// Usa o bean "emailExecutor"
}
}
3.3 Utilização
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
private final EmailService emailService;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
// Cria o pedido (síncrono)
Order order = orderService.createOrder(request);
// Envia o email (assíncrono - não bloqueia)
emailService.sendEmailAsync(
request.getEmail(),
"Order Confirmation",
"Your order #" + order.getId() + " has been created"
);
// Retorna imediatamente
return ResponseEntity.ok(order);
}
@PostMapping("/bulk-email")
public ResponseEntity<String> sendBulkEmails(@RequestBody List<String> emails) {
// Envia vários emails em paralelo
List<CompletableFuture<Boolean>> futures = emails.stream()
.map(email -> emailService.sendEmailWithResult(email, "Newsletter"))
.collect(Collectors.toList());
// Aguarda todos os resultados
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. Casos de Uso Comuns
4.1 Limpeza de Dados Expirados
@Component
public class CleanupTasks {
private final SessionRepository sessionRepository;
private final TempFileService tempFileService;
// Limpar sessões expiradas a cada hora
@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);
}
// Limpar arquivos temporários todos os dias às 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 Geração de Relatórios
@Component
public class ReportTasks {
private final ReportService reportService;
private final EmailService emailService;
// Relatório diário às 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");
}
// Relatório semanal na segunda-feira às 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 Sincronização de Dados
@Component
public class SyncTasks {
private final ExternalApiService externalApi;
private final ProductRepository productRepository;
// Sincronizar preços a cada 15 minutos
@Scheduled(fixedRate = 900000) // 15 minutos
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;
// Verificar serviços externos a cada minuto
@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. Gerenciamento de Erros
5.1 Try-Catch nas Tarefas
@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);
// Não relança para evitar parar o 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);
// Enviar alerta, métricas, etc.
alertService.sendAlert("Scheduled task failed: " + t.getMessage());
});
scheduler.initialize();
taskRegistrar.setTaskScheduler(scheduler);
}
}
6. Controle Condicional
6.1 Ativar/Desativar por Configuração
@Component
@ConditionalOnProperty(name = "app.scheduling.enabled", havingValue = "true")
public class ScheduledTasks {
// Essas tarefas só executam se app.scheduling.enabled=true
}
6.2 Controle por Profile
@Component
@Profile("!test") // Não em ambiente de teste
public class ProductionScheduledTasks {
@Scheduled(cron = "0 0 0 * * *")
public void productionOnlyTask() {
// Executa apenas em produção
}
}
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() {
// Apenas uma instância no cluster executa esta tarefa
log.info("Running hourly task on this instance");
}
}
7. Resumo
Anotações
| Anotação | Uso |
|---|---|
@EnableScheduling | Ativa o scheduling |
@EnableAsync | Ativa métodos async |
@Scheduled | Marca um método agendado |
@Async | Marca um método assíncrono |
Opções @Scheduled
| Opção | Descrição |
|---|---|
fixedRate | Intervalo desde o início |
fixedDelay | Intervalo desde o fim |
cron | Expressão Cron |
initialDelay | Delay inicial |
zone | Timezone para Cron |
Boas Práticas
- Sempre trate exceções nas tarefas
- Use pools de threads dedicados
- Monitore a execução das tarefas
- Evite tarefas muito longas
- Use locking em ambiente de cluster
- Faça log do início e fim das tarefas