O Timer Service não é referenciado nos livros clássicos de padrões de projeto como o livro do GoF. No entanto, este modelo de programação é bastante utilizada e empregada nas aplicações atuais da plataforma plataforma Java EE.
Uma grande parte das aplicações empresariais precisam executar tarefas baseadas em um calendário de eventos ou em um tempo agendado. Essa tarefa pode ser, por exemplo, a geração de relatórios semanais contendo as atividades do usuário, o envio a um cliente de um lembrete de e-mail, entre outras tarefas que podem variar de simples a complexas. De forma geral, o Timer Service permite que os desenvolvedores possam programar eventos em horários específicos ou em intervalos regulares dentro da própria plataforma Java EE.
No restante do artigo veremos como implementar este padrão na nova plataforma Java EE 7.
Padrão Timer Service
As plataformas Java EE e Java SE não ofereciam soluções embutidas para as operações baseadas no tempo. Porém, algumas ferramentas desenvolvidas por terceiros permitiam esse tipo de desenvolvimento de aplicações como, por exemplo, a a ferramenta Quartz que ainda hoje é bastante utilizada principalmente no ambiente Java SE. Contudo, essas ferramentas para operações baseadas no tempo ainda não permitiam um desenvolvimento mais direto, pois além de ter que fazer o download da biblioteca, instalar a biblioteca, implementar as interfaces e ainda era preciso configurar arquivos XML.
A partir da especificação do EJB 2.1 foram introduzidos os serviços de timer (ou temporizador), porém no Java SE ainda não existe um serviço de timer podendo ser utilizado o Quartz que é utilizado tanto na plataforma Java SE quanto na plataforma Java EE.
Mais funcionalidades e melhorias foram adicionadas na especificação EJB 3.2 (último lançamento) para o Timer Service o que o tornou ainda mais simples e robusto. Entre as inovações tem-se a introdução das anotações @Schedule e @Schedules além das adições de expressões.
O Timer Service executa em um container como um serviço e registra o Enterprise JavaBeans (EJB) para os retornos de chamada (métodos de callbacks). Ele rastreia os temporizadores que existem e os seus respectivos agendamentos, e também persiste o temporizador no caso de um servidor ser desligado ou de uma falha.
Nesse caso, a única coisa que o desenvolvedor precisa saber é agendar a tarefa.
O Timer Service passou por diversas melhorias ao longo tempo desde o EJB 2.1 passando pelo EJB 3.0, EJB 3.1 e EJB 3.2.
Na próxima seção veremos como implementar o Timer Service na plataforma Java EE 7.
Implementando Timer Service na plataforma Java EE
Existem dois tipos de temporizadores na plataforma Java EE 7, são eles: os temporizadores Automáticos e os temporizadores Programáticos.
Os temporizadores automáticos são configurados através da implantação (deployment) de um Enterprise Java Bean (EJB) que contém um método anotado com @Schedule ou @Schedules. O método anotado é invocado pelo agendador do container em tempos especificados, ou em intervalos de tempos definidos através de argumentos das anotações. Tais métodos são referidos como métodos de retorno de chamada (ou métodos de callback). O temporizador começa a contar assim que o EJB é implantado, ou seja, assim que é feito o deploy do EJB.
Um temporizador programático por sua vez é configurado em tempo de execução por uma chamada de método na lógica de negócio. O temporizador pode ser configurado dinamicamente e invocado a qualquer hora. Assim sendo, o temporizador será iniciado quando a lógica de programação determinar que ele deve começar.
Nas próximas seções será melhor explicado as diferenças entre os temporizadores automáticos e programáticos.
Timers Automáticos
Qualquer método anotado com @Schedule é chamado pelo container que por sua vez aplica as configurações especificadas nos atributos da anotação.
Segue na Listagem 1 um exemplo de uso da anotação @Schedule com alguns dos seus atributos.
Listagem 1. Exemplo de uso da anotação @Schedule.
@Schedule(second="*/1", minute="*", hour="*")
public void executaTarefa() {
System.out.println("Tarefa executada com sucesso!");
}
Nesse trecho de código pode-se verificar que o método executaTarefa é anotado com @Schedule indicando que o container deveria definir um temporizador no deployment desse método baseando-se nos valores definidos nos atributos da anotação.
No exemplo acima a expressão contida em cada um dos atributos indica que o container invocará o método executaTarefa uma vez a cada segundo.
Por padrão, todos os temporizadores são persistidos e restaurados após o desligamento ou o travamento de um servidor. Se o atributo opcional "persistent" estiver configurado como "false" o temporizador é resetado na reinicialização do servidor. Dois atributos adicionais podem ser configurados, são eles: “info” e “timezone”. O atributo “info” permite que um desenvolvedor forneça uma descrição quando for realizada uma chamada ao método “getInfo” da interface Timer. Se o atributo “timezone” for definido, esse fuso horário será respeitado quando o temporizador for executado. Caso contrário, será utilizado o fuso horário do servidor.
Segue na Listagem 2 um exemplo de utilização do atributo "timezone" e do atributo "info".
Listagem 2. Exemplo de utilização dos atributos info e timezone no Schedule.
@Schedule(hour = "23", minute = "59", timezone = "CET", info = "Gera Relatório Noturno.")
public void executaTarefa() {
System.out.println("Tarefa executada com sucesso!");
}
No código anterior o método "executaTarefa" é chamado ás 23:59 no fuso horário da Europa Central, desconsiderando assim o fuso horário do servidor em que o deploy foi realizado. Uma chamada ao método “getInfo” retorna o texto configurado no atributo "info", ou seja, se obteria como retorno "Gera Relatório Noturno".
Também podem ser configurados temporizadores mais completos usando a anotação @Schedules (agora não mais @Schedule) através da utilização de múltiplas expressões conforme mostra o exemplo da Listagem 3.
Listagem 3. Exemplo de utilização do Schedules.
@Schedules({
@Schedule(dayOfMonth = "1"),
@Schedule(dayOfWeek = "Mon,Tue,Wed,Thu,Fri", hour = "8")
})
public void executaTarefa() {
System.out.println("Tarefa executada com sucesso!");
}
Conforme o exemplo mostrado o temporizador é acionado no primeiro dia de cada mês e de segunda a sexta às 08:00.
Já o código da Listagem 4 mostra um exemplo completo de um temporizador automático.
Listagem 4. Exemplo completo de um temporizador automático.
package br.com.devmedia.temporizador;
import javax.ejb.Schedule;
import javax.ejb.Schedules;
public class ExemploTemporizador {
@Schedules({
@Schedule(dayOfMonth = "1"),
@Schedule(dayOfWeek = "Mon,Tue,Wed,Thu,Fri", hour = "8")
})
public void executaTarefa() {
System.out.println("Tarefa executada com sucesso!");
}
}
Uma desvantagem do temporizador automático é que seu agendamento está definido no momento da implantação (deploy) e não pode ser alterado enquanto o aplicativo está sendo executado. Felizmente, há uma solução para esta situação através de um temporizador programático. Um temporizador programático permite a definição do agendamento a qualquer momento durante a execução, esse temporizador será melhor analisado na próxima seção.
Timers Programáticos
Os temporizadores programáticos são criados em tempo de execução através da chamada de um dos seus métodos "create" da interface. O exemplo da Listagem 5 mostra um dos métodos “create” disponíveis na interface.
Listagem 5. Exemplo de uso do método “createTimer” para criar um temporizador programático.
public void setTemporizador() {
timerService.createTimer(30000, "Novo Temporizador");
}
Quando o método setTemporizador é invocado pela aplicação tem-se a criação de um temporizador que chama um método “timeout” no mesmo bean após a duração especificada de 30,000 milissegundos.
Um método “timeout” é identificado pela anotação @Timeout e deve obedecer a certos requisitos como: (i) ele não deve lançar exceções ou retornar um valor, (ii) não precisa receber parâmetros, porém se receber este deve ser do tipo javax.ejb.Time e por fim (iii) deve existir apenas um método "timeout".
Segue na Listagem 6 um exemplo de um método anotado com @Timeout que obedece aos requisitos especificados.
Listagem 6. Exemplo de utilização de um método timeout.
@Timeout
public void exemploTimeout() {
System.out.println("Tarefa executada com sucesso!");
}
O container CDI (Context Dependency Injection) injeta uma referencia para o TimerService em uma variável de instância anotada com @Resource. A seguir temos um exemplo do container injetando na variável de instância timerService.
@Resource
TimerService timerService;
Os códigos anteriores poderiam ser colocados juntos em um único bean onde a aplicação chama o método setTemporizador e um método "timeout" que será chamado após 30 segundos. Segue na Listagem 7 um exemplo de como poderíamos implementar esse timer programático
Listagem 7. Exemplo de implementação de um Timer programático.
package br.com.devmedia.temporizador;
import javax.annotation.Resource;
import javax.ejb.Timeout;
import javax.ejb.TimerService;
public class ExemploTemporizadorProgramatico {
@Resource
TimerService timerService;
public void setTemporizador(){
timerService.createTimer(30000, "Novo Temporizador.");
}
@Timeout
public void executaTarefa() {
System.out.println("Tarefa executada com sucesso!");
}
}
Existem ao total quatro métodos de criação de temporizadores na interface TimerService com um total de dez assinaturas.
No exemplo a seguir tem-se o método createIntervalTimer que cria um temporizador que é acionado em determinada data e, em seguida, a cada dez segundos:
createIntervalTimer(new Date(), 10000, new TimerConfig());
No próximo exemplo tem-se o método createSingleActionTimer que cria um temporizador que é acionado após um segundo.
createSingleActionTimer(1000, new TimerConfig());
Já no exemplo abaixo tem-se o método createTimer que que cria um temporizador que é acionado após 30 segundos:
createTimer(30000, "Novo Temporizador Programatico!");
No exemplo abaixo tem-se o método createCalendarTimer que cria um temporizador que dispara a cada 10 segundos:
createCalendarTimer(new ScheduleExpression().second("*/10").minute("*").hour("*"));
Todos os métodos além do método "createCalendarTimer" podem receber como primeiro parâmetro uma duração em milissegundos ou uma data. Isso configura o ponto em que o cronômetro é disparado. Segue na Listagem 8 um exemplo.
Listagem 8. Exemplo de utilização do método de criação createSingleActionTimer.
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy ‘at’ HH:mm");
Date date = formatter.parse("01/07/2015 at 17:00");
timerService.createSingleActionTimer(date, new TimerConfig());
Neste exemplo, o método “timeout” é disparado às 17:00 do dia 01 de julho de 2015.
Se for necessário um temporizador pode utilizar o método createCalendarTimer. Esse método recebe um ScheduleExpression que é configurando usando expressões conforme é discutido na próxima seção. Segue na Listagem 9 um exemplo.
Listagem 9. Exemplo de utilização de expressões com ScheduleExpression.
ScheduleExpression expression = new ScheduleExpression();
expression.second("*/10").minute("*").hour("*");
timerService.createCalendarTimer(expression);
Neste código, o agendamento está configurado para disparar a cada dez segundos de cada minuto e de cada hora.
Todos os métodos de criação retornam um objeto Timer que representa o temporizador. O objeto Timer tem o método “getHandle” que retorna um identificador serializado para o temporizador. O objeto identificador pode ser mantido em um banco de dados ou memória. Posteriormente poderíamos recuperar o objeto identificador e retornar uma referência para o temporizador invocando o método “getTimer”. Com este objeto em mãos, pode-se recuperar informações úteis sobre o temporizador. Detalhes sobre o agendamento do temporizador podem ser recuperados através do método “getSchedule” que retorna um objeto “ScheduleExpression”, onde pode-se utilizar os métodos getters para cada atributo. Por exemplo, o método “getMinute()” retorna o valor configurado para o atributo “minute”.
O método “getNextTimeout” retorna o ponto de quando o temporizador será acionado, enquanto que o método “getTimeRemaining” retorna os milissegundos antes de expirar o temporizador. O método “isCalendarTimer” retorna “true” se o temporizador foi configurado por um objeto “ScheduleExpression”, porém deve ser chamado o método “getSchedule” para determinar se o temporizador foi configurado, caso contrário o método “isCalendarTimer” lança uma exceção IllegalStateException. Para determinar informações sobre o estado persistente do temporizador é utilizado o método “isPersistent”. Da mesma forma pode-se obter informações sobre o tempo chamando o método “getInfo”. Os temporizadores são automaticamente cancelados quando eles expiram. Para cancelar temporizadores agendados basta chamar o método “cancel” no objeto Timer.
Expressões em Timers
Os temporizadores programáticos e os temporizadores automáticos podem utilizar os atributos para configurar o temporizador. Nos temporizadores automáticos podemos configurar os atributos nas anotações, enquanto que temporizadores programáticos utiliza-se os métodos da classe ScheduleExpression.
Existem algumas restrições que devem ser respeitadas para cada um dos atributos. O atributo “second” aceita um ou mais segundos dentro de um minuto com valores entre 0 e 59, o atributo “minute” aceita um ou mais minutos dentro de uma hora com valores entre 0 e 59, o atributo “hour” aceita uma ou mais horas dentro de um dia com valores entre 0 e 23, o atributo “dayOfWeek” aceita um ou mais dias dentro de uma semana com valores entre 0 e 7 sendo 7 domingo, o atributo “dayOfMonth” aceita um ou mais dias dentro de um mês com valores entre 1 e 31 além de aceitar os valor -7 até -1 que se refere aos dias a partir do final do mês, Last para o último dia, ou ainda 1st, 2nd, 3rd, etc, e por fim, valores entre Sun e Sat, o atributo “month” aceita um ou mais meses dentro de um ano com valores entre 1 e 12 e com valores entre Jan e Dec, e por fim, o atributo “year” aceita um ano como 2014, 2015, etc.
O valor default para os valores dos atributos é zero para o tempo e asterisco para os atributos que não são numéricos. O asterisco é um espaço reservado para todos os valores possíveis para um atributo. Para configurar um temporizador que seja disparado a cada hora pode-se utilizar, por exemplo, a expressão hour="*", o mesmo vale para o método hour da classe ScheduleExpression em que seria utilizado o método “hour("*")”. Outra possibilidade é utilizar uma lista ou um intervalo como a expressão dayOfMonth="1, 15, last" para configurar o disparo no primeiro, no décimo quinto e no último dia de cada mês. Por sua vez a expressão hour="8‐18" representa cada hora das 08:00 até às 18:00. Para especificar intervalos e aumentá-los com um ponto de partida poderia utilizar-se a expressão hour="8/1" que dispara a cada hora iniciando às 08:00 diferente da expressão hour="*/12" que dispara a cada 12 horas.
Também é possível configurar apenas intervalos para segundos, minutos e horas. Assim sendo, a expressão second="10" dispara a cada dez segundos, hour = "2" disparada a cada duas horas, minute = "15" dispara a cada 15 minutos, dayOfWeek="Mon, Fri" dispara a cada Segunda e Sexta a meia noite, a expressão dayOfWeek="0‐7", hour="8" dispara a cada dia às 8 horas da manhã, dayOfMonth="‐7" dispara seis dias antes de terminar cada mês a meia noite, a expressão dayOfMonth="1st Mon", hour="22" dispara na primeira segunda de cada mês às 10 horas da noite, a expressão Month="Mar", dayOfMonth="15" dispara no décimo quinto dia de março do próximo ano, a expressão year="2015", month="May" dispara dia primeiro de Março de 20015 a meia noite.
No EJB 3.2 é possível acessar todos os temporizadores ativos no módulo EJB. Isso inclui temporizadores programáticos e automáticos.
Na Listagem 10 temos um exemplo de como pode-se implementar essa funcionalidade no EJB.
Listagem 10. Exemplo de como recuperar e manipular os temporizadores.
package br.com.devmedia.temporizador;
import java.util.Collection;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.Timer;
import javax.ejb.TimerService;
@Singleton
@Startup
public class ExemploTodosTemporizadores {
@Resource
TimerService timerService;
@PostConstruct
public void gerenciaTemporizadores(){
Collection<Timer> timers = timerService.getAllTimers();
for(Timer t : timers){
System.out.println("Informações do Temporizador: " + t.getInfo());
System.out.println("Tempo Restante: " + t.getTimeRemaining());
//cancela o temporizador
t.cancel();
}
}
}
Veja que o bean é instanciado na inicialização e o método gerenciaTemporizadores é chamado. Nesse método é recuperada uma coleção de todos os temporizadores ativos e após isso itera-se em toda a coleção imprimindo a informação do temporizador e o número de milissegundos que falta para expiração do temporizador programado. Por fim, cancela-se o temporizador.
Bibliografia
[1] Erich Gamma, Ricard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994).
[2] Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra. Head First Design Patterns. O'Reilly Media, 2004.
[3] Murat Yener, Alex Theedom. Proffesional Java EE Design Patterns. Wrox, 2015.