Agendando tarefas com o TimerService do EJB 3.1

A versão 3 dos Enterprise Java Beans trouxe grandes mudanças e muitas simplificações para o desenvolvedor. O forte uso de anotações e convenções, que deixaram os XMLs complexos opcionais, entrou no JPA como forma padrão de persistência para substituir os burocráticos entity beans e a injeção de dependências melhora o design para não depender de lookups acoplados.

O EJB 3.1 foi um passo na mesma direção. Nessa versão as interfaces locais ficaram opcionais, os EJBs podem ser utilizados dentro de um WAR, o que simplifica o empacotamento (EAR não é mais preciso), e várias outras novidades.

Uma das melhorias do EJB 3.1 está relacionada com o agendamento de tarefas dentro do servidor de aplicação, o que é o foco desse post. Mas, para ser exato, o agendamento já era possível nas versões anteriores do EJB (entrou na versão 2.1 da especificação), mas foi muito aperfeiçoado no EJB 3.1.

Como funcionava com EJB 3.0/2.1

Primeiro era preciso definir o método que era chamado quando o @Timeout de um agendamento ocorria. Para isso podemos usar um Session Bean Stateless:

@Stateless @Remote(Agendador.class) //pode ser @Local também
public class AgendadorBean implements Agendador {

	@Timeout // no EJB 2.1 implementava a interface javax.ejb.TimedObject 
	public void timeout(Timer timer) {
		System.out.println("Timeout: " + timer.getInfo());
	}

Para agendar a execução desse método, usamos o TimerService. Com ele podemos definir o agendamento e executar um método anotado com @Timeout apenas uma vez, ou em intervalos (single-action ou interval-action). O TimerService pode ser injetado, com EJB3, da seguinte maneira:

@Stateless @Remote(Agendador.class)
public class AgendadorBean implements Agendador {

	@Resource //no EJB 2.1 era preciso usar ejbContext.getTimerService() 
	private TimerService timerService;

	public void agenda() {
		//definir o agendamento - daqui 10s, cada 20s
		this.timerService.createTimer(10*1000L, 20*1000L, "alguma info");
	}
}

O método createTimer(..) é sobrecarregado e possui variações, mas não tem como fazer mais do que definir single-action-timer ou interval-action.

O que melhorou com EJB 3.1

No EJB 3.1 o TimerService ganhou métodos para deixar o agendamento mais preciso, baseado em expressões de calendar. Isso é bem parecido com o que o framework Quartz permite no Java, e as expressões cron:

public void agenda() {
	// cada segunda e quarta as 8:30
	ScheduleExpression expression = new ScheduleExpression();
	expression.dayOfWeek("Mon,Wed");		
	expression.hour("8");
	expression.minute("30");
	this.timerService.createCalendarTimer(expression);
	System.out.println("Agendado: " + expression);
}

Com essas expressões podemos definir intervalos bem precisos e a mesma definição também pode ser feita de forma declarativa, através da anotação @Schedule. Com ela nem é necessário usar @Timeout. O método anotado com @Schedule é invocado quando o timeout ocorre:

//dentro do session bean
@Schedule(dayOfWeek="Mon,Wed", hour="8", minute="30")
void agendado() {
	System.out.println("agendado pela anotacao @Schedule");
}

O servidor de aplicação chama então o agendado() periodicamente. Importante saber que qualquer timer que definir um intervalo (interval-action-timer) é persistido e será recuperado quando o servidor reiniciar. Mas podemos deixar o agendamento não persistente também:

//dentro do session bean
@Schedule(dayOfWeek="Mon,Wed", hour="8", minute="30", persistent=false)
void agendado() {
	System.out.println("agendado pela anotacao @Schedule");
}

ou ser for agendado programaticamente:

ScheduleExpression expression = new ScheduleExpression();
//..
TimerConfig config = new TimerConfig();
config.setPersistent(false);
	
this.timerService.createCalendarTimer(expression, config);

Poderíamos ainda melhorar o exemplo usando @Singleton e @Startup, outras novidades do EJB 3.1. Isso pode ser útil se for utilizado o agendamento através do @Schedule não persistente. E claro, isso é apenas mais uma das facilidades que o container EJB e o servidor de aplicação podem oferecer, todos vistos no treinamento Java EE avançado e Web Services.

13 Comentários

  1. Lucas Murata 31/10/2011 at 11:59 #

    Otimo artigo.

    Realmente EJB 3.1 ficou excelente.
    Uma pena que EJB deixou imagens tão negativas oriundas do seu passado, penso que todo desenvolvedor Java terá receio de usar EJB por conta disso.

    Eu particularmente gosto muito desse novo EJB e uso-o em meus projetos, inclusive Timer Service que foi comentado aqui.

  2. Ivan Rodrigues 31/10/2011 at 16:00 #

    O TimerService e o EJB 3.1 realmente ficaram muito bons, mas, contudo, parece que é sempre a mesma história a especificação correndo atrás de algum projeto que já existe e tem uma certa solidez. Não sou um especialista em agendamento de tarefa, mas o Quartz parece ainda estar na frente do TimerService, assim como o Hibernate e Toplink na frente da JPA 2.

  3. Nico Steppat 31/10/2011 at 16:32 #

    @Lucas

    Realmente é uma pena… é por isso que não podemos basear nossas decisões na afinidade ou hype do mercado.

    @Ivan,

    o que vc falou é, pra mim, a essência do JCP/JSRs (ou deve ser a essência). Não inovar ou inventar e sim pegar ideias maduras do mercado e padronizar elas (Hibernate pra JPA por exemplo, ideias do Spring pra EJB, Seam pra JSF ou Seam pra CDI etc). Inovação vem do mercado e os devs decidem se aprovam ou não.

    Abraços!

  4. Paulo Silveira 31/10/2011 at 16:42 #

    Eu ia responder exatamente o que o @Nico disse: infelizmente ou felizmente, a JCP é para pegar o que ha de melhor e criar um padrão em cima disso.

    Inovacao vem do mercado, a especificacao da JCP.

    Quando a JCP tentou inovar, sempre acabou se atrapalhando. É o caso perfeito dos falecidos JDO 1 e JDO 2.

  5. Raphael Lacerda 31/10/2011 at 17:04 #

    Bahhh, lembrar de JDO é teeeeenssssso!!!

    E é bom salientar que as vezes até para copiar demora um tempo também. CDI só saiu em 2009. A JSR 310 ainda nem sinal dela… e por ai vai

  6. Guilherme Moreira 01/11/2011 at 19:34 #

    Vale lembrar que não podemos mais falar que JDO faleceu, afinal ele pode voltar à tona com os banco de dados não relacionais, vide Google App Engine.

    Excelente Post Nico!

  7. Sérgio Lopes 02/11/2011 at 23:40 #

    Pra mim, JDO tá morto. Só é usado no GAE porque é o padrão do Datanucleus – aliás, outra coisa que ninguém usa.

    Não vejo o JDO sendo usado em outros lugares, NoSQL etc…

  8. Daniel 09/11/2011 at 11:36 #

    Sem duvidas a melhoria no Ejb 3.1 é visivelmente valiosa. Ja uso essa funcionalidade a um bom tempo (lembro ate do comentario que questionei no post de @Paulo Silveira, sobre agendamentos com Quartz).
    Mas como o @Sérgio Lopes falou, nao vejo JDO ser usado, mas NoSQL, ainda penso ser uma idéia promissora, diante da imensidão de possibilidades que esta quebra de paradigma proporciona. Com certeza, vai demorar ainda para conseguirmos mensurar ate onde vai estas tecnologias, mesmo porque os sistemas ainda se baseiam quase por completamente em persistencia em banco de dados ou ate mesmo simples arquivos de texto (nao tao simples se falarmos da Receita Federal). Por esse ultimo, acredito que podemos ter uma grande mudança pela frente.

  9. Rafael 20/06/2012 at 10:42 #

    Muito com esse POST, adiantou meu lado!
    Obrigado!

  10. Danilo Magrini 26/04/2013 at 11:22 #

    Alguém sabe me informar se usando @Schedule o método é reexecutado em caso do servidor cair? Por exemplo se ele está programado para executar as 00:00 e o servidor cai as 23:00 e volta as 00:00 ele deveria reexecutar caso o atributo persistent = true? Estou fazendo testes mas não está reexecutando. A documentação oficial (http://docs.oracle.com/javaee/6/tutorial/doc/bnboy.html) afirma isso em Programmatic Timers :

    “If a persistent timer expires while the server is down, the container will call the @Timeout method when the server is restarted.”

    Porém para Automatic Timers (Schedule) apenas diz que “The optional persistent element takes a Boolean value and is used to specify whether the automatic timer should survive a server restart or crash. By default, all automatic timers are persistent.”

    Não diz explicitamente que ele é reexecutado caso o tempo tenha expirado.

  11. Enio 25/06/2013 at 08:32 #

    tava testando o seguinte tenho um EJB:
    @Singleton
    @Startup
    public class ProcessamentoArquivosJob

    @Inject
    private ParametrosFacade parametrosFacade;

    com o metodo :

    @Schedule(hour = “8”, minute = “29”)
    public void runGerarEventoClientes() {
    }

    ocorre que a injeção não é feita dentro do EJB, tenho procurado a dias uma referencia a isso e nao consigo, encontrar uma luz, poderia me ajduar ?

  12. Gustavo Bitencourt 21/05/2014 at 11:02 #

    @Danilo Magrini, conseguiu achar mais informações sobre esse detalhe que comentasse?

    Pois acredito que seja reexecutado, pois testei localmente, e quando dou start no Jboss ele reexecuta.
    Porém, eu estou com um problema, que talvez tenha algo relacionado a isso.

    Não consegui mapear o erro ainda, porém, os detalhes são os seguintes:
    O projeto tem um Schedule que é executado as 00:01.
    Eu não sei por que d#@$@,ocorre algum problema que ele faz a mudança nos objetos parcial e depois simplesmente para de executar.
    Porém, não bastando esse problema, se não for restartado o Jboss, ele não executa mais os temporizadores/schedule.

    Alguém está passando por isso?

Deixe uma resposta