Agendando tarefas com Scheduled do Spring

Agendando tarefas com Scheduled do Spring

Introdução

Estou desenvolvendo um sistema de vendas web utilizando Spring Boot e agora o cliente pediu algumas funcionalidades um pouco peculiares...

Ele deseja saber como está o fluxo de vendas por um determinado período, por exemplo, a cada hora, ou então, a cada dia... Como será que podemos fazer essa rotina que irá realizar essas tarefas agendadas?

Banner da Escola de Programação: Matricula-se na escola de Programação. Junte-se a uma comunidade de mais de 500 mil estudantes. Na Alura você tem acesso a todos os cursos em uma única assinatura; tem novos lançamentos a cada semana; desafios práticos. Clique e saiba mais!

Utilizando a API Timer do Java

Inicialmente, em uma implementação utilizando as APIs disponíveis em Java, poderíamos fazer uso da classe Timer:


Timer timer = new Timer();

Em seguida, faríamos uso do método scheduleAtFixedRate() que espera uma implementação da classe abstrata TimerTask como primeiro parâmetro e mais dois parâmetros do tipo long que indicam o início da execução e tempo constante para reexecutar sucessivamente em milisegundos:


Timer timer = new Timer(); 
timer.scheduleAtFixedRate(new TimerTask() {
    @Override public void run() { 
    System.out.println("Executando a primeira vez em " +
     "1 segundo e as demais a cada 5 segundos!"); }

    }, 1000, 5000);

Em outras palavras, neste exemplo estaríamos executando o código, inicialmente, depois de um segundo, e então, todas as vezes após 5 segundos!

Considerando esse exemplo, para que conseguíssemos realizar aquela consulta para o cliente que verifica o fluxo de vendas de um determinado período, como por exemplo, uma hora, faríamos algo como:


public class VerificadorDePegamentos {

    private final long SEGUNDO = 1000;
    private final long MINUTO = SEGUNDO * 60; 
    private final long HORA = MINUTO * 60;

    public void verificaPorHora() { 
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {

            @Override public void run() { // Código que realiza a consulta de fluxo de vendas }

            }, 0, HORA); 
        }

    }

Em outras palavras, poderíamos criar uma classe responsável em ficar verificando as vendas realizadas a cada uma hora e nos devolveria a resposta que o nosso cliente tanto espera!

Utilizando a annotation @Scheduled

Porém, veja a quantidade de código que tivemos que escrever para que fosse possível realizar essa tarefa! Neste caso, já que estamos utilizando o Spring, será que não tem uma forma mais fácil?

Felizmente, temos a annotation @Scheduled que já faz todo o papel do Timer e TimerTask pra nós! Em outras palavras, faríamos a mesma coisa que fizemos anteriormente da seguinte maneira:

public class VerificadorDePegamentos {

    private final long SEGUNDO = 1000; 
    private final long MINUTO = SEGUNDO * 60; 
    private final long HORA = MINUTO * 60;

    @Scheduled(fixedDelay = HORA) 
    public void verificaPorHora() { 
        // Código que realiza a consulta de fluxo de vendas 
    }

}

Veja que o código diminuiu e muito! Porém, o que está acontecendo agora? No momento em usamos a annotation @Scheduled indicamos ao Spring que aquele método será agendado.

Além disso, veja que está sendo enviado o parâmetro fixedDelay, esse parâmetro é equivalente ao último parâmetro do método scheduleAtFixedRate() da classe Timer, ou seja, é o tempo fixo que sempre vai ficar rodando!

Também podemos usar outros parâmetros caso for necessário, segue alguns exemplos:

  • initialDelay: Configura a espera inicial do agendamento, basicamente ele é equivalente ao primeiro parâmetro do método scheduleAtFixedRate(). Ele também recebe valores long indicando os milisegundos.

  • fixedRate: Configura um período fixo entre o início e fim da execução do agendamento.

Observe que estamos fazendo a mesma coisa que fazíamos antes, porém com um código muito mais simples de ser compreendido!

Configurando o agendamento no Spring

Conhecemos o Spring como um framework que facilita e muito a vida da pessoa desenvolvedora, isto é, com pouca configuração ele é capaz de gerenciar o ciclo de vida dos objetos entre diversas outras coisas...

Porém, para que isso seja possível, pelo menos uma vez, precisamos indicar ao Spring que ele tenha esse tipo de comportamento! Portanto, para rodarmos o agendamento das tarefas com o Spring, será necessário realizar algumas configurações também.

Transformando a classe em Component

A primeira delas é fazer com que a classe que esteja utilizando a @Scheduled seja gerenciada pelo Spring. A forma mais fácil de fazer isso é basicamente tornando-a um Component por meio da annotation @Component:


@Component public class VerificadorDePegamentos {

    //Atributos

    @Scheduled(fixedDelay = HORA) 
    public void verificaPorHora() { 
        // Código que realiza a consulta de fluxo de vendas 
    }

}

Habilitando o agendamento

Agora que a classe está sendo gerenciada, precisamos apenas informar ao Spring que queremos habilitar o agendamento para ela por meio da annotation @EnableScheduling:


@Component @EnableScheduling 
public class VerificadorDePegamentos {

e//Atributos

    @Scheduled(fixedDelay = HORA) 
    public void verificaPorHora() { 
        // Código que realiza a consulta de fluxo de vendas 
    }

}

Pronto, a nossa classe já está configurada! Agora basta apenas reiniciar o seu projeto do Spring Boot que o agendamento rodará!

Lembrando que para realizar um teste mais perceptível utilize a base dos segundos no parâmetro fixedDelay ou fixedRate. ;)

Agendamento por um determinado horário do dia

Veja que agora conseguimos solucionar a situação em que o cliente queria ver a quantidade de vendas a cada 1 hora, ou seja, fizemos um agendamento a cada X tempo. Entretanto, se o cliente chegar e falar o seguinte:

"Quero também um relatório que me mostre quantas vendas foram realizadas até a meio dia de todos os dias!"

E agora? Como poderíamos fazer? Bom, uma solução inicial seria fazer com que a nossa task iniciasse às 12:00h, e então, ela tivesse um tempo fixo de 24:00h, certo? Dessa forma, todas as vezes que passe às 24:00h seria a meio dia de cada dia...

Entretanto, veja que esse tipo de solução não é adequada, afinal, teremos algumas complicações dependendo de alguns cenários, por exemplo:

  • Aplicação (servidor) reiniciando: O período de 24:00h não seria mais válido.
  • Mudança de horário: Dependendo do cambio do horário teríamos 1 hora a mais ou a menos, ou seja, mais um preocupação.

E agora? Será que tem alguma forma de contornar essa situação?

Agendamento com o Cron

Considerando todos esses cenários, o ideal seria fazer uso de algum tipo de recurso que permitisse de fato configurar de uma forma mais específica, os minutos, horas ou até mesmo o dia que o determinado agendamento será executado.

Um dos mecanismos utilizados em sistemas baseados no Unix (como é o caso do Linux), para o agendamento de tarefas de uma forma mais pontual, é o Cron.

Basicamente o Cron é um programa que permite realizarmos a execução de jobs em um determinado período, como por exemplo: a cada A segundos, B minutos, C horas no dia D execute uma tarefa!

Todas as definições são feitas por meio de um recurso chamado Cron Expression.

Exemplos do uso do Cron

Abaixo seguem alguns exemplos de execução do Cron utilizando Cron Expression:

  • A cada segundo: ``` "1"

  • Todos os dias que o horário tiver ao menos 01 segundos: ``` "1"

  • A cada hora, baseada no relógio, que esteja com 00 minutos e segundos todos os dias: ``` "0 0"

    
    
    Exemplos: 00:00:00, 01:00:00...

Significado de cada campo do Cron

A princípio o Cron pode parecer algo muito bizarro, porém, cada campo possui um significado bem especificado. Abaixo é listado o que cada campo significa junto com seus valores esperados:

 A B C D E F
  • A: Segundos (0 - 59).
  • B: Minutos (0 - 59).
  • C: Horas (0 - 23).
  • D: Dia (1 - 31).
  • E: Mês (1 - 12).
  • F: Dia da semana (0 - 6).

Note também que nos exemplos foram usados o *, esse caracter indica que para o campo específicado qualquer valor será considerado.

Observação: Quando fazemos uso do "/" concatenando algum valor, indicamos o seguinte: para cada (/) X (valor numérico) repetição acione, em outras palavras, */1 indica que se qualquer valor do campo segundo mudar será acionado, logo, se fosse */2, a cada 2 vezes que o campo segundo mudar de valor, será acionado.

Usando o Cron no Scheduled

Tendo conhecimento de todo o poder do Cron nessa pequena demonstração, seria superbacana usar um recurso similar ao Cron no @Scheduled...

Felizmente podemos também fazer uso dele por meio do parâmetro cron, ou seja, para executar aquele agendamento que pegaria as vendas todos os dias ao meio dia poderia ser feito da seguinte maneira:


@Component @EnableScheduling 
public class VerificadorDePegamentos {

    private final long SEGUNDO = 1000; 
    private final long MINUTO = SEGUNDO * 60; 
    private final long HORA = MINUTO * 60;

    @Scheduled(cron = "0 0 12") 
    public void verificaPorHora() { 
        System.out.println(LocalDateTime.now()); 
        // Código que realiza a consulta de fluxo de vendas 
    }

}

Nessa Cron Expression temos uma execução de todos os dias ao meio dia!

Cuidados ao usar o Cron

Embora o Cron resolva o nosso problema de executar uma tarefa em um período bem específico, existe uma situação que pode ser problemática, que é justamente a Time Zone. Em outras palavras, se o local no qual o seu software estiver hospedado tiver um Time Zone diferente do seu, o horário de meio dia pode ser que não seja igual ao seu!

Dessa forma, o Cron causaria mais problemas do que resolveria... E agora?

Como sempre a gente vê, há sempre aquela flexibilidade em configurações quando usamos o Spring, e com o agendamento de tarefas não seria diferente! Ou seja, podemos também definir sob qual Time Zone a @Scheduled será executada por meio do parâmetro zone! Então ficaria da seguinte maneira:


@Component @EnableScheduling 
public class VerificadorDePegamentos {

    private static final String TIME_ZONE = "America/Sao_Paulo";
     // atributos

    @Scheduled(cron = "0 0 12 \* \* \*", zone = TIME_ZONE) public void verificaPorHora() { 
        System.out.println(LocalDateTime.now()); 
        // Código que realiza a consulta de fluxo de vendas 
    }

}

Veja que agora estou enviando o Time Zone America/Sao_Paulo que é justamente o Time Zone utilizado aqui em São Paulo - SP, portanto, o agendamento poderá ser executado sem a preocupação de onde o projeto esteja alocado!

Querendo conhecer o Spring? Que tal começar hoje mesmo na Formacao de Desenvolvedor Java Web com Spring? Nesta seleção de cursos, você irá aprender a criar uma aplicação Web bem robusta desde o zero com um dos frameworks mais famosos do mundo Java! Veja também todo nosso conteúdo de Java, Web, Spring, Hibernate, Testes e mais.

Alex Felipe
Alex Felipe

Alex é instrutor e desenvolvedor e possui experiência em Java, Kotlin, Android. Atualmente cria conteúdo no canal https://www.youtube.com/@AlexFelipeDev.

Veja outros artigos sobre Programação