A execução de tarefas agendadas é uma prática comum no desenvolvimento de software, utilizada para automatizar processos como envio de e-mails, notificações por SMS, ou funcionalidades de e-commerce como recebimento automático de pedidos e gerenciamento de publicações.
Existem diversas abordagens para implementar tarefas agendadas em Java:
- Timer (
java.util.Timer): Uma classe nativa do Java que permite agendarTimerTasks. Embora funcional, é menos utilizada em cenários modernos. - ScheduledExecutorService (
java.util.concurrent.ScheduledExecutorService): Introduzido no Java 1.5, é uma solução baseada em pool de threads. Cada tarefa agendada é executada por uma thread do pool, permitindo a execução concorrente e independente das tarefas. - Frameworks de Terceiros: Soluções como Quartz, elastic-job e xxl-job são robustas e adequadas para ambientes distribuídos, mas podem apresentar maior complexidade na configuração e curva de aprendizado.
- Spring Task: Desde o Spring 3.0, o framework oferece uma solução leve e fácil de usar, similar ao Quartz, utilizando a anotação
@Scheduled.
Este artigo foca na implementação de tarefas agendadas utilizando ScheduledExecutorService, explorando o controle dinâmico sobre seu início e parada.
Implementação com Spring Task
Antes de mergulharmos na solução com ScheduledExecutorService, apresentamos uma breve demonstração do Spring Task com anotações:
- Adicione a anotação
@EnableSchedulingna classe principal da sua aplicação Spring. - Aplique a anotação
@Scheduleda métodos específicos em classes gerenciadas pelo Spring.
import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.TimeUnit;
// Assumindo que LocalDateTimeUtil e outros helpers estão disponíveis // import com.example.utils.LocalDateTimeUtil;
@Component // Ou outra anotação de componente Spring public class ScheduledTasks {
// @Scheduled(cron = "0/1 * * * * *") // Executa a cada segundo
public void taskOne() throws InterruptedException {
System.out.println("Task One executing at: " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) +
", Thread: " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(10); // Simula uma tarefa demorada
}
// @Scheduled(cron = "0/1 * * * * *") // Executa a cada segundo
public void taskTwo() {
System.out.println("Task Two executing at: " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) +
", Thread: " + Thread.currentThread().getName());
}
// @Scheduled(cron = "0/1 * * * * *") // Executa a cada segundo
public void taskThree() {
System.out.println("Task Three executing at: " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) +
", Thread: " + Thread.currentThread().getName());
}
}
</div>Sem configurações adicionais, ao executar o projeto, as tarefas agendadas (`taskOne`, `taskTwo`, `taskThree`) serão executadas sequencialmente na mesma thread (geralmente denominada `scheduling-1`), e uma tarefa só iniciará após a conclusão da anterior.
Para habilitar a execução paralela, é necessário configurar um bean do tipo `ThreadPoolTaskScheduler`.
### Configuração do ThreadPoolTaskScheduler
<div>```
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@Configuration
public class SchedulerConfig {
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10); // Define o número de threads no pool
scheduler.setThreadNamePrefix("custom-scheduler-"); // Prefixo para os nomes das threads
return scheduler;
}
}
Implementação com ScheduledExecutorService para Controle Dinâmico
Esta seção detalha como usar ThreadPoolTaskScheduler (uma implementação de ScheduledExecutorService) para iniciar e parar tarefas agendadas dinamicamente.
1. Configuração Inicial
Certifique-se de que a anotação @EnableScheduling esteja presente na classe de inicialização da sua aplicação Spring.
2. Criação da Lógica da Tarefa e Controle
Crie os seguintes componentes:
- Um controlador REST para expor os endpoints de controle.
- Um serviço para abstrair a lógica de agendamento.
- Uma implementação do serviço que gerencia o
ThreadPoolTaskSchedulere as tarefas.
Controlador (TaskController.java)
package com.example.controller;
import com.example.service.TaskControlService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*;
@RestController @RequestMapping("/schedule") public class TaskController {
private final TaskControlService taskControlService;
public TaskController(TaskControlService taskControlService) {
this.taskControlService = taskControlService;
}
@PostMapping("/start")
public ResponseEntity<String> startScheduledTask() {
boolean started = taskControlService.startTask();
if (started) {
return ResponseEntity.ok("Scheduled task started successfully.");
}
return ResponseEntity.badRequest().body("Failed to start scheduled task.");
}
@PostMapping("/stop")
public ResponseEntity<String> stopScheduledTask() {
boolean stopped = taskControlService.stopTask();
if (stopped) {
return ResponseEntity.ok("Scheduled task stopped successfully.");
}
return ResponseEntity.badRequest().body("Failed to stop scheduled task.");
}
}
</div>#### Serviço (`TaskControlService.java`)
<div>```
package com.example.service;
public interface TaskControlService {
/**
* Starts the scheduled task.
* @return true if the task was started or already running, false otherwise.
*/
boolean startTask();
/**
* Stops the scheduled task.
* @return true if the task was stopped or already stopped, false otherwise.
*/
boolean stopTask();
}
package com.example.service;
import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Service;
import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.ScheduledFuture;
@Slf4j @Service public class TaskControlServiceImpl implements TaskControlService {
private ScheduledFuture<?> taskFuture;
private final ThreadPoolTaskScheduler taskScheduler;
// Injeta o ThreadPoolTaskScheduler configurado como um Bean
public TaskControlServiceImpl(ThreadPoolTaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
@Override
public boolean startTask() {
// Cancela a tarefa existente, se houver, para evitar múltiplas instâncias
if (taskFuture != null && !taskFuture.isDone()) {
taskFuture.cancel(true);
log.info("Previous scheduled task cancelled.");
}
// Define a expressão cron. Idealmente, isso viria de uma configuração externa.
// Exemplo: Executar a cada 15 segundos.
String cronExpression = "*/15 * * * * *";
// Cria e agenda a tarefa
Runnable taskToRun = new MyScheduledRunnable();
CronTrigger trigger = new CronTrigger(cronExpression);
this.taskFuture = taskScheduler.schedule(taskToRun, trigger);
log.info("Scheduled task started with cron: {}", cronExpression);
return true;
}
@Override
public boolean stopTask() {
if (taskFuture != null && !taskFuture.isDone()) {
boolean cancelled = taskFuture.cancel(true); // Tenta cancelar a execução
if (cancelled) {
log.info("Scheduled task successfully cancelled.");
} else {
log.warn("Could not cancel scheduled task.");
}
return cancelled;
}
log.info("Scheduled task was not running or already stopped.");
return true; // Considera como sucesso se já estava parado
}
// Classe interna para a lógica da tarefa agendada
private static class MyScheduledRunnable implements Runnable {
@Override
public void run() {
// Lógica de negócio a ser executada
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
log.info("Executing background job at {} using thread {}", timestamp, Thread.currentThread().getName());
// ... insira aqui a lógica da sua tarefa ...
}
}
}
</div>### 3. Controle via API
Após iniciar a aplicação, você pode usar ferramentas como Postman ou cURL para enviar requisições POST para os endpoints `/schedule/start` e `/schedule/stop` para controlar a execução da tarefa agendada.
Anexo: Expressões Cron
----------------------
Expressões Cron são strings que definem padrões de agendamento. Uma expressão cron comumente usada possui 6 campos (ou 7, incluindo o ano opcional), separados por espaços:
[Segundo] [Minuto] [Hora] [Dia do Mês] [Mês] [Dia da Semana] [Ano (opcional)]
### Sintaxe e Caracteres Especiais:
- `*`: Coringa, representa todos os valores possíveis para o campo.
- `?`: Não especifica um valor. Usado quando não se quer determinar um valor para um campo específico (geralmente em Dia do Mês ou Dia da Semana).
- `-`: Define um intervalo de valores (ex: `10-12` para as horas 10, 11 e 12).
- `,`: Especifica valores múltiplos (ex: `MON,WED,FRI` para segunda, quarta e sexta).
- `/`: Incremento. Usado para especificar um intervalo de repetição (ex: `5/15` em segundos significa a cada 15 segundos, começando aos 5 segundos: 5, 20, 35, 50).
- `L`: Último. No campo Dia do Mês, significa o último dia do mês. No campo Dia da Semana, significa Sábado. Se precedido por um número (ex: `6L` no Dia da Semana), indica o último dia da semana especificado (ex: o último Sexta-feira do mês).
- `W`: Dia de semana mais próximo. No campo Dia do Mês, especifica o dia de semana mais próximo da data indicada (ex: `15W` aciona no dia de semana mais próximo do dia 15).
- `#`: Indica a ocorrência de um dia da semana em uma determinada semana do mês (ex: `6#3` para o terceiro Sábado do mês).
### Exemplos de Expressões Cron:
- `*/15 * * * * *`: A cada 15 segundos.
- `0 30 14 * * ?`: Diariamente às 14:30.
- `0 0 12 ? * WED`: Toda quarta-feira ao meio-dia.
- `0 15 10 L * ?`: No último dia de cada mês, às 10:15.
- `0 15 10 ? * 6L`: Na última sexta-feira de cada mês, às 10:15.
- `0 0 12 * * ? 2025`: Ao meio-dia de todos os dias durante o ano de 2025.