Agendando tarefas com Scheduled do Spring

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?

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 do desenvolvedor, 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 {

	//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 * * * * *"
    

    Exemplos: 11:22:01, 11:23:01…

  • 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!

O que achou de agendar as tarefas por meio do @Scheduled do Spring? Deixe seu comentário e compartilhe conosco sua experiência 😉

Querendo conhecer o Spring? Que tal começar hoje mesmo na carreira 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!

Fique por dentro

(Última atualização em: 9 de janeiro de 2017)

Content Editor at Alura and Software Developer

  • Fanime Fartoon

    Muito bacana, considerando a facilidade do Scheduled, existe algum motivo para alguém usar o Quartz?

    • Alex Felipe

      Olá Fanime, tudo bem? Nunca fiz uso do Quartz, portanto, não sou o mais indicado para afirmar se faz sentido o uso ou não… Porém, estive conversando com alguns amigos e eles mencionaram que uma coisa legal do Quartz é que ele possibilita alterar o agendamento em modo runtime, ou seja, sem parar ou reiniciar a aplicação. Fora isso, não sei qual seria um bom motivo para fazer uso do Quartz.

      Abraços.

    • Fernando Boaglio

      O Quartz existe desde 2005 e ele tem a flexibilidade de criar jobs em runtime, imagine por exemplo um job diferente para cada usuário logado, já vi sistema assim, e o Spring cuidava de todo o resto, mas jobs era só com o Quartz mesmo.

      • Fanime Fartoon

        Entendi, tem um sistema na empresa que estão usando o Quartz, mas é só pra disparar o serviço de madrugada, sem configurações em runtime. Como o sistema está todo em Spring vou avaliar a possibilidade de migrar pro Scheduled e remover a dependência.

        Obrigado a todos que ajudaram =)

  • Eduardo Medeiros Branquinho

    Artigo muito bem feito. Parabéns Alex, pra quem não conhecia, deve facilitar bastante a vida.
    E respondendo ao Fanime abaixo, eu já utilizei o Quartz em pequenos projetos JavaSE, aonde não tinha a facilidade do Scheduled.

    • Alex Felipe

      Obrigado pelo feedback Eduardo! Em um projeto que participei tive que fazer uso do Cron para rodar uma task a meia noite de todos os dias… Era um projeto Spring, ou seja, foi aí que descobri sobre essa feature, de fato ajudou muito! 😉

      Abraços.

  • Josue Freitas

    Excelente post! Estava precisando justamente de uma funcionalidade dessa!

    • Alex Felipe

      Fico muito contente que tenha gostado Josue! Obrigado pelo feedback 😉

  • Glêsio Santos

    Alex, este artigo é bem legal, parabéns! Fico impressionado com a capacidade e flexibilidade do Spring. Poderia tira uma simples duvida? Estou tentando mudar status de documento de forma automática que encontra-se no status de enviado ou tratamento, beleza consigo realizar a busca no banco com o agendamento de acordo o seus status, consigo comparar a data de prazo com data atual do sistema tudo beleza. O meu problema é quando tenho um grande volume de update e só consegue realizar 1. Exemplo encontra 3 resultado na lista, faço a interação no foreach, analiso a data onde caso sendo verdade altera para vencido. Gostaria de saber, se neste caso teria realizar um agendamento para consulta e outra para realizar os updates ou somente desse resolveria o caso, no caso de sim qual seria o procedimento para isto. Obs.: o meu metodo esta anotado com @Scheduled(cron = “0 0 12 * * MON-FRI”)

    Abraços

    • Alex Felipe

      Oi Glêsio, tudo bem?

      Pelo o que eu entendi o seu processo se baseia em buscar os documentos, verificar o estado atual e mudar caso for necessário. Acredito que a abordagem é essa mesmo, você vai pegar os documentos e fazer o update necessário. Claro, para otimizar sua solução atual seria uma implementação via multi threads mesmo, dessa forma você executaria os passos em paralélo, não sei te passar algo já pronto que faria isso pra você, a princípio seria via API de Thread do Java mesmo. A minha única observação sobre o paralelimos é que o maior problema é você editar o mesmo conteúdo em threads diferentes, pois isso pode gerar problemas de concorrência que é bem comum nesse tipo de solução.

      Abraços.

      • Glêsio Santos

        Alex, muito obrigado pelas dicas e valeu pelos esclarecimentos

  • Ivanildo Ferreira da Silva Fil

    Olá Alex. Bacana seu Post. Gostaria de saber se tem como eu receber por parâmetro os valores do fixedRate, fixedDelay ou do Cron?

    • Alex Felipe

      Oi Ivanildo, tudo bem? Obrigado pelo feedback!

      Consegue me explicar uma situação na qual seria necessário receber os valores por parâmetro? Assim fica mais fácil de eu verificar as possibilidades que atendam a sua necessidade

      []s

  • Luiz Fagner Zordan

    Muito bom o seu artigo, bem esclarecedor.
    Só fiquei com uma dúvida em relação a ambiente clusterizado, como funcionaria?
    Assim como no Quatz seria necessário uma tabela de lock? Ou existe alguma outra forma de contar isso?
    Abraço!

    • Alex Felipe

      Oi Luiz, tudo bem?

      Muito obrigado pelo feedback, fico contente que tenha gostado!

      Sobre essa parte de cluster de fato eu não sei te responder com precisão, pois ainda não usei essa feature em um ambiente do gênero… De qualquer forma, é possível dar uma olhada na documentação do Spring que dedica uma seção exclusiva para Scheduling dentro do framework.

      []s

Próximo ArtigoGuia do sucesso: por que você precisa investir em capacitação profissional na área de tecnologia?