Pular para o conteúdo principal

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ãoDescrição
*Todos os valores
*/15De 15 em 15
0-23Intervalo de valores
1,15Lista de valores
?Não especificado (dia)
LÚltimo (dia do mês)
WDia útil mais próximo
#N-ésimo dia da semana (2#1 = primeira segunda-feira)

2.5 Exemplos Cron Comuns​

ExpressãoDescriçã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 * * SUNTodos 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çãoUso
@EnableSchedulingAtiva o scheduling
@EnableAsyncAtiva métodos async
@ScheduledMarca um método agendado
@AsyncMarca um método assíncrono

Opções @Scheduled​

OpçãoDescrição
fixedRateIntervalo desde o início
fixedDelayIntervalo desde o fim
cronExpressão Cron
initialDelayDelay inicial
zoneTimezone para Cron

Boas Práticas​

  1. Sempre trate exceções nas tarefas
  2. Use pools de threads dedicados
  3. Monitore a execução das tarefas
  4. Evite tarefas muito longas
  5. Use locking em ambiente de cluster
  6. Faça log do início e fim das tarefas

Próximo Capítulo: WebSockets

Voltar ao Módulo 14