我想知道在应用程序作用域bean中使用Timer
是否可以。
示例,假设我想创建一个计时器任务,每天一次向每个注册会员发送一堆电子邮件。我试图尽可能多地使用JSF,我想知道这是否可以接受(我知道它有点奇怪)。
到目前为止,我已在ServletContextListener
内使用了上述所有内容。 (我不想使用任何应用程序服务器或cron作业,我想保留
我的网络应用程序中的上述内容。)
是否有一种聪明的JSF方式可以做到这一点,还是应该坚持使用旧模式?
答案 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.util.Timer
至于在JSF托管bean中使用java.util.Timer
,你应该绝对不使用旧式Timer
,而是使用现代ScheduledExecutorService
。 Timer
具有以下主要问题,使其不适合在长期运行的Java EE Web应用程序中使用(引自Java Concurrency in Practice):
Timer
对系统时钟的变化很敏感,ScheduledExecutorService
不是。Timer
只有一个执行线程,因此长时间运行的任务可以延迟其他任务。 ScheduledExecutorService
可以配置任意数量的线程。TimerTask
中抛出的任何运行时异常都会杀死一个线程,从而导致Timer
死亡,即计划任务将不再运行。 ScheduledThreadExecutor
不仅可以捕获运行时异常,还可以根据需要处理它们。抛出异常的任务将被取消,但其他任务将继续运行。除了书中的报价,我还能想到更多的缺点:
如果您忘记明确cancel()
Timer
,那么它会在取消部署后继续运行。因此,在重新部署后,创建一个新线程,再次执行相同的工作。等等。它已成为一个“火与忘记”,你现在不能以编程方式取消它。您基本上需要关闭并重新启动整个服务器以清除以前的线程。
如果Timer
线程未标记为守护程序线程,则它将阻止webapp的取消部署和服务器的关闭。你基本上需要硬杀服务器。主要的缺点是webapp无法通过例如执行优雅的清理工作。 contextDestroyed()
和@PreDestroy
方法。
@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;
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();
}
}