产生JSF托pipebean中的线程以使用计时器进行计划任务

我想知道是否可以在应用程序范围的bean中使用Timer

例如,可以说我想创build一个计时器任务,每天向每个注册成员发送一堆电子邮件。 我试图尽可能多地使用JSF,我想知道这是否可以接受(我知道,这听起来有点奇怪)。

到目前为止,我已经在ServletContextListener使用了上述ServletContextListener 。 (我不想使用任何应用程序服务器或cron作业,我想保留上面的东西在我的web应用程序。)

有没有一个聪明的JSF这样做的方式还是应该坚持旧的模式?

介绍

至于从JSF托pipebean中产生一个线程, 只有当你想在你的视图中通过#{managedBeanName}或在@ManagedProperty("#{managedBeanName}")其他托pipebean中引用它@ManagedProperty("#{managedBeanName}") 。 您应该确保实现@PreDestroy以确保在webapp将要closures时closures所有这些线程,就像在contextDestroyed()方法中一样(是的,您做了?)。 另请参见在JSF托pipebean中启动新线程是否安全?

切勿在Java EE中使用java.util.Timer

至于在JSF托pipebean中使用java.util.Timer绝对不应该使用旧式的Timer ,而应该使用现代的ScheduledExecutorServiceTimer具有以下主要问题,使其不适用于长时间运行的Java EE Web应用程序(引自“ Java并发实践” ):

  • Timer对系统时钟的变化很敏感, ScheduledExecutorService不是。
  • Timer只有一个执行线程,所以长时间运行的任务可以延迟其他任务。 ScheduledExecutorService可以configuration任意数量的线程。
  • TimerTask引发的任何运行时exception都会杀死一个线程,从而导致Timer死机,即计划任务将不再运行。 ScheduledThreadExecutor不仅捕获运行时exception,但它可以让你处理它们,如果你想。 抛出exception的任务将被取消,但其他任务将继续运行。

除了书中的引言之外,我可以想到更多的缺点:

  • 如果您忘记显式cancel() Timer ,那么在取消部署之后它会继续运行。 所以在重新部署一个新的线程后,再次做同样的工作。 等等。 它现在已经成为一个“火灾和遗忘”,你不能以编程方式取消它。 你基本上需要closures并重新启动整个服务器清除以前的线程。

  • 如果Timer线程没有标记为守护进程线程,那么它将阻止Web应用程序的取消部署和服务器closures。 你基本上需要努力杀死服务器。 主要的缺点是web应用程序将无法通过例如contextDestroyed()@PreDestroy方法执行优雅的清理。

EJB可用? 使用@Schedule

如果你的目标是Java EE 6或更新版本(例如JBoss AS,GlassFish,TomEE等,因此不是像Tomcat这样的准系统JSP / Servlet容器),那么可以使用带有@Schedule方法的@Singleton EJB。 这样容器就会担心通过ScheduledExecutorService集中和销毁线程。 所有你需要的是以下的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. } } 

这是必要的@EJB托pipe的豆:

 @EJB private BackgroundJobManager backgroundJobManager; 

EJB不可用? 使用ScheduledExecutorService

没有EJB,你需要手动使用ScheduledExecutorService 。 应用程序作用域的托pipebean实现看起来像这样:

 @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(); } } 

SomeDailyJob看起来像这样:

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

如果您不需要在视图或其他托pipe的bean中引用它,那么最好使用ServletContextListener使其与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(); } }