Agendando tarefas em Java com Quartz Scheduler e TimerTask

Veja neste artigo como usar o TimerTask e o Quartz Scheduler para agendar tarefas no Java.

O uso de tarefas agendadas é muito comum em sistemas de grande e médio porte, principalmente por possuírem diversas funcionalidades ligadas a automatização de tarefas sem intervenção humana. É comum vermos a utilização de ferramentas como crontab (Linux) para agendamento de tarefas, sejam elas em Java ou uma outra linguagem.

O objetivo deste artigo é trabalhar com agendamento de tarefas internamente no Java, sem utilizar ferramentas como o crontab. Assim desacoplamos a necessidade de uma estrutura externa para que nosso sistema possa funcionar de maneira adequada.

Agendadores de Tarefa

TimerTask

A linguagem Java nativamente já conta com um agendador de tarefa que pode ser explorado através da classe TimerTask.

A classe TimerTask é uma classe abstrata que implementa a interface Runnable, utilizada quando precisamos trabalhar com multi-thread, como mostra a Listagem 1.

Listagem 1. TimerTask

package java.util; public abstract class TimerTask implements Runnable { final Object lock = new Object(); int state = VIRGIN; static final int VIRGIN = 0; static final int SCHEDULED = 1; static final int EXECUTED = 2; static final int CANCELLED = 3; long nextExecutionTime; long period = 0; protected TimerTask() { } public abstract void run(); public boolean cancel() { synchronized(lock) { boolean result = (state == SCHEDULED); state = CANCELLED; return result; } } public long scheduledExecutionTime() { synchronized(lock) { return (period < 0 ? nextExecutionTime + period : nextExecutionTime - period); } } }

O método que nos interessa na listagem acima é o “public abstract void run()”, ele será o responsável por executa nossa lógica em intervalos pré-configurados. Como você já deve ter notado o princípio para uso da classe TimeTask é estendê-la e implementar o método run(), já que este é abstrato e sua implementação é obrigatória. Então é exatamente isso que faremos, como mostra a Listagem 2.

Listagem 2. Estendendo a classe TimerTask

import java.util.Date; import java.util.TimerTask; public class Agendador extends TimerTask { Date instanteAtual; @Override public void run() { instanteAtual = new Date(); System.out.println(instanteAtual); } }

O exemplo acima é simples apenas para fins didáticos. Nossa classe Agendador estende a classe TimerTask implementando uma lógica dentro do método run(), que é mostrar o instante atual em que ele está sendo executado. Para um primeiro momento este exemplo é útil para conseguirmos visualizar como o TimerTask funciona.

Agora precisamos iniciar nossa classe Agendador, caso contrário, o método run() jamais será chamado, como mostra a Listagem 3.

Listagem 3. Iniciando nosso TimerTask

import java.util.Timer; public class StartAgendador { /** * @param args */ public static void main(String[] args) { Timer timer = new Timer(); Agendador agendador = new Agendador(); timer.schedule(agendador, 0, 1000); while(true){ System.out.println("Alerta de execução ..."); try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }

Acima temos nossa classe StartAgendador que já sugere o início ao nosso TimerTask. Vamos passo a passo explicar o seu funcionamento:

  1. Instanciamos o objeto “timer” do tipo Timer, que é responsável por dar início ao nosso Agendador:
    Timer timer = new Timer();
  2. Logo em seguida instanciamos nossa classe Agendador atribuindo sua instância ao objeto “agendador”.
    Agendador agendador = new Agendador();
  3. Até o momento nada fora do comum. Agora utilizar o objeto timer para chamar o método schedule() que de fato dá início a execução da nossa thread Agendador. O método schedule() que utilizamos tem a seguinte assinatura:
    public void schedule(TimerTask task, long delay, long period)

Ele recebe um objeto TimerTask, que em nosso caso é a classe Agendador, um valor do tipo “long” com o tempo em millisegundos antes da thread começar a ser executada, e por último um valor também do tipo “long” com o intervalo de tempo em milisegundos para execução repetida da thread, ou seja, de quanto em quanto tempo ela será executada. Em nosso caso usamos os parâmetros:

timer.schedule(agendador, 0, 1000);

Que nos diz que a nossa tarefa será executada de um em um segundo sem atraso para iniciar, ou seja, assim que esta linha for executada a tarefa será inicializada imediatamente.

O próximo trecho da nossa lógica é apenas para garantir que a nossa thread Agendador está sendo executada concorrentemente ao nosso fluxo principal. Usamos o while(true) para ficarmos eternamente nessa execução e mostramos no console a mensagem “Alerta de execução...” a cada dois segundos:

while(true){ System.out.println("Alerta de execução ..."); try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }

Assim você perceberá que enquanto mostramos a mensagem “Alerta de execução” a cada dois segundos, dentro desses dois segundos serão mostrados dois datas com diferença de um segundo que foi disparado pelo método run() da classe Agendador. Assim você poderá ver visualmente que duas threads estão trabalhando de forma concorrente. Vejamos como ficará a saída da execução acima, como mostra a Listagem 4.

Listagem 4. Saída provável da Listagem 3

Alerta de execução ... Mon Mar 16 20:21:48 BRT 2015 Mon Mar 16 20:21:49 BRT 2015 Alerta de execução ... Mon Mar 16 20:21:50 BRT 2015 Mon Mar 16 20:21:51 BRT 2015 Mon Mar 16 20:21:52 BRT 2015 Alerta de execução ... Mon Mar 16 20:21:53 BRT 2015 Mon Mar 16 20:21:54 BRT 2015 Alerta de execução ... Mon Mar 16 20:21:55 BRT 2015 Mon Mar 16 20:21:56 BRT 2015 Alerta de execução ... Mon Mar 16 20:21:57 BRT 2015 Mon Mar 16 20:21:58 BRT 2015 Alerta de execução ... Mon Mar 16 20:21:59 BRT 2015 Mon Mar 16 20:22:00 BRT 2015 Alerta de execução ... Mon Mar 16 20:22:01 BRT 2015 Alerta de execução ... Mon Mar 16 20:22:02 BRT 2015

A também outras formas de inicializar nosso Agendador com o Timer, passando a data em que queremos iniciar, em vez do tempo em “long”. Para isso há a seguinte assinatura do método:

public void schedule(TimerTask task, Date firstTime, long period)

Onde a lógica continua sendo a mesma com a única diferença que você terá que passar como parâmetro a data de início da tarefa agendada.

Para muitos casos o TimerTask é uma ferramenta incrível que ajuda a resolver problemas que antes precisariam de toda uma infraestrutura tecnológica para serem resolvidos, assim não ficamos “escravos” de ferramentas externas ao Java, trazendo todo necessário para dentro de um só local, realizando um verdadeiro “inbox”, tudo em um único lugar.

Mas há casos ainda mais específicos que exigem um maior nível de controle e detalhes. É claro que isso é possível com o TimerTask porém o trabalho seria um pouco mais árduo e talvez até desnecessário visto que já temos API's prontas para tal tarefa, como é o caso do Quartz Scheduler.

Quartz Scheduler

Inicialmente o Quartz vai parecer um pouco mais complexo do que o TimerTask e isso se dá ao fato deste possuir muitos recursos complexos do ponto de vista funcional.

Este trabalha com o padrão de projeto Factory, representado pela interface SchedulerFactory. Em vez de implementarmos essa interface e desenvolver toda lógica necessária para criação de um Scheduler para o Quartz, nós podemos usar uma das duas classes que faz isso: StdSchedulerFactory ou DirectSchedulerFactory.

O Scheduler possui dois método que delimitar o início e um fim, são eles: start() e o shutdown(). Logo depois do método start() você pode começar a agendar suas tarefas e quando o método shutdown() é chamado elas param de serem executadas.

JOB

O Quartz trabalha com o conceito de JOB para implementar as funcionalidades que serão executadas. Lembra que em seções anteriores vimos o uso do método run() no TimerTask? A lógica é quase a mesma aqui, nós implementamos a interface Job, onde o método execute() seria análogo ao run() mostrado em seções anteriores. Observe a Listagem 5.

Listagem 5. Implementando a interface Job

import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class ValidadorJob implements Job { @Override public void execute(JobExecutionContext arg0) throws JobExecutionException { //Simula validações System.out.println("Validando dados duplicados no banco. At "+new Date()); System.out.println("Deletando registros com mais de 10 dias sem uso. At "+new Date()); } }

Fique atento também aos imports, para não importar classes de pacotes errados. O método execute() é quem irá “guardar” a nossa lógica que deverá ser executada em determinado instante de tempo. Em nosso caso estamos simulando algumas validações que poderiam ocorrer de tempos em tempos: Apagar registros duplicados e registros com mais de 10 dias sem uso (Geralmente usuários que não se logaram há muito tempo), claro que esta é só uma situação hipotética.

Você verá mais a frente que para instanciar nosso Job, utilizaremos a classe JobBuilder, onde passaremos a implementação do Job através do método newJob(), a identidade no método withIdentity() e a criação através do build(). Pode parecer complexo no início mais ficará mais claro quando mostrarmos na prática o uso destes métodos.

Essa é a parte mais fácil, pois como já vimos no TimerTask a lógica é quase a mesma apenas substituindo o run() pelo execute(). O trabalho maior será fazer sua lógica interna ao execute() e isso depende, obviamente, de cada regra de negócio.

TRIGGER

Ainda temos outro artefato para conhecer antes de começar a utilizar o Quartz de fato. Este usa o conceito de “Triggers” para configurar o agendamento de tarefas e disparar as tarefas. Veremos que o Quartz define alguns tipos de triggers para utilizarmos, mas em nosso caso usaremos o CronTrigger que simula o famoso crontab do linux.

Para construirmos nossa trigger os passos são quase os mesmos do Job, usamos o TriggerBuild(), também o withidentity() para definir o nome da nossa trigger, os dados do agendamento através do withSchedule() e a criação/instanciação da trigger através do build().

O uso do CronTrigger é uma simulaçao ao crontab, e para isso precisamos entender como funciona a expressão de agendamento, ou expressão cron. Para criar uma expressão cron deve-se conhecer a ordem dos parâmetros que são: Segundos, Minutos, Horas, Dia do Mês, Mês, Dia da Semana e Ano (Opcional). Imagine como posições no vetor, onde cada uma dessas posições corresponde exatamente as informações já mencionadas (segundos, minutos e etc).

Se você deseja que a execução ocorra a cada 10 segundos, faremos assim:

0/10 * * * * ?

Você pode questionar porque o correto não é apenas “10” e sim “0/10”. Se você usasse apenas o valor “10” então estaria dizendo: “Eu quero que toda vez que nos 10 segundos de qualquer minuto execute essa função”. O mesmo ocorre se for por apenas “0”, você estaria especificando a execução da tarefa sempre que for zero segundos de todos os minutos, o que na verdade seria executado de um em um minuto.

Então você já deve ter percebido que para executar de um em m segundo, o correto seria:

0/1 * * * * ?

Executando o Quartz

Depois de entendido o conceito de Job e Trigger para o Quartz, vamos de fato começar a utilizá-lo, como mostra a Listagem 6.

Listagem 6. Usando o Quartz

import org.quartz.CronScheduleBuilder; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.impl.StdSchedulerFactory; public class QuartzApp { /** * @param args */ public static void main(String[] args) { SchedulerFactory shedFact = new StdSchedulerFactory(); try { Scheduler scheduler = shedFact.getScheduler(); scheduler.start(); JobDetail job = JobBuilder.newJob(ValidadorJob.class) .withIdentity("validadorJOB", "grupo01") .build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("validadorTRIGGER","grupo01") .withSchedule(CronScheduleBuilder.cronSchedule("0/1 * * * * ?")) .build(); scheduler.scheduleJob(job, trigger); } catch (SchedulerException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }

Vamos as explicações da listagem acima.

  1. Fizemos o uso da implementação do padrão factory pela classe StdSchedulerFactory, assim temos como capturar uma instância de Scheduler.
    SchedulerFactory shedFact = new StdSchedulerFactory(); try { Scheduler scheduler = shedFact.getScheduler();
  2. O objeto Scheduler mostrado no passo 1 nos permite iniciar o monitoramento das tarefas e agendas novas tarefas conforme for necessário, para iniciar usamos:
    scheduler.start();
  3. Logo em seguida começamos a definir no Job, usando o JobBuilder. O nosso identificador (withidentity) será “validadorJOB” e o grupo será o “grupo01”.
    JobDetail job = JobBuilder.newJob(ValidadorJob.class) .withIdentity("validadorJOB", "grupo01") .build();
  4. Por fim, criamos nossa Trigger necessária para definir o intervalo de execução exato do nosso Job:
    Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("validadorTRIGGER","grupo01") .withSchedule(CronScheduleBuilder.cronSchedule("0/1 * * * * ?")) .build();
    Colocamos a Trigger com o identificador “validadorTRIGGER” e no mesmo grupo do “validadorJOB” definindo anteriormente. Em nosso caso definimos a execução de um em um segundo. Usamos o CronScheduleBuilder que é responsável por trabalhar com o Crontrigger que mencionamos logo no início.
    Temos então os dois pontos importantes definidos: O Job e o Trigger. Agora precisamos adicionar nossa tarefa ao Scheduler já criado no passo 1.
  5. Adicionamos o objeto job e o trigger ao scheduler, assim ele saberá qual tarefa deverá ser executada e em qual instante de tempo:
    scheduler.scheduleJob(job, trigger);

Ao executar o código acima temos a seguinte saída, apresentada na Listagem 7.

Listagem 7. Saída provável da Listagem 6

log4j:WARN No appenders could be found for logger (org.quartz.impl.StdSchedulerFactory). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. Validando dados duplicados no banco. At Mon Mar 16 21:25:43 BRT 2015 Deletando registros com mais de 10 dias sem uso. At Mon Mar 16 21:25:43 BRT 2015 Validando dados duplicados no banco. At Mon Mar 16 21:25:44 BRT 2015 Deletando registros com mais de 10 dias sem uso. At Mon Mar 16 21:25:44 BRT 2015 Validando dados duplicados no banco. At Mon Mar 16 21:25:45 BRT 2015 Deletando registros com mais de 10 dias sem uso. At Mon Mar 16 21:25:45 BRT 2015 Validando dados duplicados no banco. At Mon Mar 16 21:25:46 BRT 2015 Deletando registros com mais de 10 dias sem uso. At Mon Mar 16 21:25:46 BRT 2015 Validando dados duplicados no banco. At Mon Mar 16 21:25:47 BRT 2015 Deletando registros com mais de 10 dias sem uso. At Mon Mar 16 21:25:47 BRT 2015 Validando dados duplicados no banco. At Mon Mar 16 21:25:48 BRT 2015 Deletando registros com mais de 10 dias sem uso. At Mon Mar 16 21:25:48 BRT 2015 Validando dados duplicados no banco. At Mon Mar 16 21:25:49 BRT 2015 Deletando registros com mais de 10 dias sem uso. At Mon Mar 16 21:25:49 BRT 2015 Validando dados duplicados no banco. At Mon Mar 16 21:25:50 BRT 2015 Deletando registros com mais de 10 dias sem uso. At Mon Mar 16 21:25:50 BRT 2015

Perceba que o tempo entre execuções é de 1 segundo, o que nos mostra que o Scheduler está executando exatamente no tempo definido. Além disso, tempos um pequeno “warning” a respeito do log4j, biblioteca necessária para funcionamento do Quartz.

Bibliotecas necessárias

Para funcionamento do quartz você deve adicionar algumas bibliotecas no seu classpath, para isso na seção de downloads deste artigo você encontrar um arquivo quartz-2.1.7.tar.gz que contém uma biblioteca chamada quartz-all-2.1.7.jar, ela deverá ser adicionada ao classpath do seu projeto.

Além disso, dentro da pasta “lib” você encontrar: c3p0-0.9.1.1.jar, log4j-1.2.16.jar, slf4j-api-1.6.1.jar e slf4j-log4j12-1.6.1.jar. Adicione todas ao seu classpath.

O Quartz possui muitos recursos, você notará que dentro deste arquivo há diversos exemplos diferentes de como utilizá-lo, e você descobrirá que pode até usar o agendamento de tarefas gravando este no banco de dados em vez da memória do servidor. No próprio site oficial do Quartz você encontrará uma documentação detalhada sobre o uso de cada funcionalidade, mas isso fica cargo de você leitor caso ache necessário o estudo aprofundado desta biblioteca.

O principal objetivo deste artigo foi mostrar o uso de duas ferramentas muito úteis para agendamento de tarefas, sendo a primeira já nativa do Java (TimerTask) com funcionalidades mais simples porém eficazes e a segunda partindo de uma biblioteca externa chamada Quartz Scheduler que tem por função adicionar maior complexidade (do ponto de vista funcional) ao agendamento de tarefas.

Com isso, não precisamos depender da instalação de ferramentas externas, como o crontab, para executar classes específicas em determinado instante de tempo. Temos um maior poder e controler sobre o sistema como um todo. É claro e evidente que cada regra de negócio pode exigir formas diferentes de se trabalhar, pois assim como pode ser ideal usar o Quartz ou TimerTask, também pode ser mais ideal ainda para determinado contexto usar o Crontab como ferramenta de monitoramento, o fator é muito variável conforme o contexto em que trabalhamos.

Links

Quartz
http://quartz-scheduler.org/documentation

Artigos relacionados