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

Dim*_*man 28 jsf timer scheduled-tasks jsf-2

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

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

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

是否有一种聪明的JSF方式,或者我应该坚持使用旧模式?

Bal*_*usC 70

介绍

至于从JSF托管bean内部生成一个线程,只有你希望能够在你的视图中#{managedBeanName}或在其他托管bean中引用它时才有意义@ManagedProperty("#{managedBeanName}").您应该只确保实现@PreDestroy以确保在webapp即将关闭时关闭所有这些线程,就像在contextDestroyed()方法中一样ServletContextListener(是的,你做了吗?).另请参阅在JSF托管bean中启动新线程是否安全?

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

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

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

除了书的引用,我还能想到更多的缺点:

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

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

EJB可用吗?使用@Schedule

如果您的目标是Java EE 6或更新版本(例如JBoss AS,GlassFish,TomEE等,因此不是像Tomcat 这样的准系统JSP/Servlet容器),那么请使用@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.
    }

} 
Run Code Online (Sandbox Code Playgroud)

如果必要,可通过@EJB以下方式在托管bean中使用:

@EJB
private BackgroundJobManager backgroundJobManager;
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

SomeDailyJob这个样子的:

public class SomeDailyJob implements Runnable {

    @Override
    public void run() {
        // Do your job here.
    }

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)