Gerenciamento Dinâmico de Tarefas Agendadas com ScheduledExecutorService

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 agendar TimerTasks. 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:

  1. Adicione a anotação @EnableScheduling na classe principal da sua aplicação Spring.
  2. Aplique a anotação @Scheduled a 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 ThreadPoolTaskScheduler e 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.

Tags: java Spring ScheduledExecutorService ThreadPoolTaskScheduler cron

Publicado em 8-15 20:41