Gerando encadeamentos em um bean gerenciado JSF para tarefas planejadas usando um timer

Gostaria de saber se está tudo bem em usar o Timer dentro de beans com escopo de aplicativos.

Exemplo, digamos que eu quero criar uma tarefa de timer que envia um monte de e-mails para cada membro registrado uma vez por dia. Estou tentando usar o máximo possível de JSF e gostaria de saber se isso é aceitável (parece um pouco estranho, eu sei).

Até agora eu usei todos os ServletContextListener acima dentro de um ServletContextListener . (Eu não quero usar qualquer servidor de aplicativos ou cron job e quero manter as coisas acima dentro do meu aplicativo da web.)

Existe uma maneira inteligente de fazer isso ou devo ficar com o antigo padrão?

Introdução

Quanto a gerar um encadeamento dentro de um bean gerenciado pelo JSF, ele faria sentido se você quisesse referenciá-lo em suas visualizações por #{managedBeanName} ou em outros beans gerenciados por @ManagedProperty("#{managedBeanName}") . Você só deve certificar-se de implementar @PreDestroy para garantir que todos esses threads sejam encerrados sempre que o webapp estiver prestes a ser encerrado, como você faria no método contextDestroyed() do ServletContextListener (sim, você fez?). Veja também É seguro iniciar um novo encadeamento em um bean gerenciado pelo JSF?

Nunca use java.util.Timer no Java EE

Quanto ao uso de java.util.Timer em um bean gerenciado JSF, você absolutamente não deve usar o Timer antigo, mas o ScheduledExecutorService moderno. O Timer tem os seguintes problemas principais, o que o torna inadequado para uso em um aplicativo da Web Java EE de longa duração (citado de Java Concurrency in Practice ):

  • Timer é sensível a alterações no relógio do sistema, ScheduledExecutorService não é.
  • Timer possui apenas um encadeamento de execução, portanto, a tarefa de longa duração pode atrasar outras tarefas. ScheduledExecutorService pode ser configurado com qualquer número de threads.
  • Quaisquer exceções de tempo de execução lançadas em um TimerTask eliminam esse thread, tornando o Timer inoperante, isto é, as tarefas agendadas não serão mais executadas. ScheduledThreadExecutor não apenas detecta exceções de tempo de execução, mas também permite que você as manipule se desejar. A tarefa que emitiu exceção será cancelada, mas outras tarefas continuarão a ser executadas.

Além das citações do livro, posso pensar em mais desvantagens:

  • Se você esquecer de cancel() explicitamente cancel() o Timer , ele continuará sendo executado após a desimplementação. Então, depois de uma reimplementação, um novo encadeamento é criado, fazendo o mesmo trabalho novamente. Etcetera. Tornou-se um “fogo e esqueça” até agora e você não pode mais cancelar programaticamente. Você basicamente precisa desligar e reiniciar o servidor inteiro para limpar os threads anteriores.

  • Se o encadeamento do Timer não estiver marcado como encadeamento do daemon, ele bloqueará a desinstalação do webapp e o encerramento do servidor. Você basicamente precisa matar o servidor. A principal desvantagem é que o webapp não será capaz de realizar limpezas graciosas por meio dos contextDestroyed() e @PreDestroy .

EJB disponível? Use @Schedule

Se você segmentar o Java EE 6 ou mais recente (por exemplo, JBoss AS, GlassFish, TomEE etc. e, portanto, não um contêiner JSP / Servlet barebones como o Tomcat), use um @Singleton EJB com o método @Schedule . Dessa forma, o contêiner se preocupará em agrupar e destruir encadeamentos via ScheduledExecutorService . Tudo que você precisa é o seguinte EJB:

 @Singleton public class BackgroundJobManager { @Schedule(hour="0", minute="0", second="0", persistent=false) public void someDailyJob() { // Do your job here which should run every start of day. } @Schedule(hour="*/1", minute="0", second="0", persistent=false) public void someHourlyJob() { // Do your job here which should run every hour of day. } @Schedule(hour="*", minute="*/15", second="0", persistent=false) public void someQuarterlyJob() { // Do your job here which should run every 15 minute of hour. } } 

Isto é, se necessário, disponível em beans gerenciados pelo @EJB :

 @EJB private BackgroundJobManager backgroundJobManager; 

EJB indisponível? Use o ScheduledExecutorService

Sem o EJB, você precisaria trabalhar manualmente com o ScheduledExecutorService . A implementação do bean gerenciado com escopo de aplicativo seria algo como isto:

 @ManagedBean(eager=true) @ApplicationScoped public class BackgroundJobManager { private ScheduledExecutorService scheduler; @PostConstruct public void init() { scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(new SomeDailyJob(), 0, 1, TimeUnit.DAYS); } @PreDestroy public void destroy() { scheduler.shutdownNow(); } } 

onde o SomeDailyJob fica assim:

 public class SomeDailyJob implements Runnable { @Override public void run() { // Do your job here. } } 

Se você não precisa referenciá-lo na visualização ou em outros beans gerenciados, então é melhor usar ServletContextListener para mantê-lo dissociado do JSF.

 @WebListener public class BackgroundJobManager implements ServletContextListener { private ScheduledExecutorService scheduler; @Override public void contextInitialized(ServletContextEvent event) { scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(new SomeDailyJob(), 0, 1, TimeUnit.DAYS); } @Override public void contextDestroyed(ServletContextEvent event) { scheduler.shutdownNow(); } }