使用计时器在JSF托管bean中为计划任务生成线程

时间:2011-09-21 12:12:27

标签: jsf jsf-2 timer scheduled-tasks

我想知道在应用程序作用域bean中使用Timer是否可以。

示例,假设我想创建一个计时器任务,每天一次向每个注册会员发送一堆电子邮件。我试图尽可能多地使用JSF,我想知道这是否可以接受(我知道它有点奇怪)。

到目前为止,我已在ServletContextListener内使用了上述所有内容。 (我不想使用任何应用程序服务器或cron作业,我想保留 我的网络应用程序中的上述内容。)

是否有一种聪明的JSF方式可以做到这一点,还是应该坚持使用旧模式?

1 个答案:

答案 0 :(得分:70)

简介

至于从JSF托管bean内部生成一个线程,如果你希望能够通过#{managedBeanName}或其他托管bean在你的视图中引用它,才有意义由@ManagedProperty("#{managedBeanName}")。您应该只确保实现@PreDestroy以确保在Web应用程序即将关闭时关闭所有这些线程,就像在contextDestroyed()ServletContextListener方法中一样(是的) ,你呢?)。另请参阅Is it safe to start a new thread in a JSF managed bean?

永远不要在Java EE中使用java.util.Timer

至于在JSF托管bean中使用java.util.Timer,你应该绝对不使用旧式Timer,而是使用现代ScheduledExecutorServiceTimer具有以下主要问题,使其不适合在长期运行的Java EE Web应用程序中使用(引自Java Concurrency in Practice):

  • Timer对系统时钟的变化很敏感,ScheduledExecutorService不是。
  • Timer只有一个执行线程,因此长时间运行的任务可以延迟其他任务。 ScheduledExecutorService可以配置任意数量的线程。
  • TimerTask中抛出的任何运行时异常都会杀死一个线程,从而导致Timer死亡,即计划任务将不再运行。 ScheduledThreadExecutor不仅可以捕获运行时异常,还可以根据需要处理它们。抛出异常的任务将被取消,但其他任务将继续运行。

除了书中的报价,我还能想到更多的缺点:

  • 如果您忘记明确cancel() Timer,那么它会在取消部署后继续运行。因此,在重新部署后,创建一个新线程,再次执行相同的工作。等等。它已成为一个“火与忘记”,你现在不能以编程方式取消它。您基本上需要关闭并重新启动整个服务器以清除以前的线程。

  • 如果Timer线程未标记为守护程序线程,则它将阻止webapp的取消部署和服务器的关闭。你基本上需要硬杀服务器。主要的缺点是webapp无法通过例如执行优雅的清理工作。 contextDestroyed()@PreDestroy方法。

EJB可用吗?使用@Schedule

如果你的目标是Java EE 6或更新版本(例如JBoss AS,GlassFish,TomEE等,因而是一个准系统JSP / Servlet容器,如Tomcat),那么使用@Singleton使用@Schedule方法的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在托管bean中使用:

@EJB
private BackgroundJobManager backgroundJobManager;

EJB不可用?使用ScheduledExecutorService

如果没有EJB,您需要手动使用ScheduledExecutorService。应用程序范围的托管bean实现看起来像这样:

@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.
    }

}

如果您根本不需要在视图或其他托管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();
    }

}