我想知道在应用程序范围的Bean中使用Timer
是否可行。
例如,假设我想创建一个定时任务,每天向每个注册成员发送一批电子邮件。 我尽可能地使用JSF,并且想知道这是否可行(我知道这听起来有点奇怪)。
到目前为止,我已经在ServletContextListener
中使用了所有上述内容。(我不想使用任何应用程序服务器或cron job,并且我希望将上述内容保留在我的Web应用程序内。)
是否有一种聪明的JSF方法来完成这个任务,还是我应该坚持旧模式?
我想知道在应用程序范围的Bean中使用Timer
是否可行。
例如,假设我想创建一个定时任务,每天向每个注册成员发送一批电子邮件。 我尽可能地使用JSF,并且想知道这是否可行(我知道这听起来有点奇怪)。
到目前为止,我已经在ServletContextListener
中使用了所有上述内容。(我不想使用任何应用程序服务器或cron job,并且我希望将上述内容保留在我的Web应用程序内。)
是否有一种聪明的JSF方法来完成这个任务,还是我应该坚持旧模式?
如果您想通过#{managedBeanName}
在视图中引用它,或者在其他托管的bean中使用@ManagedProperty("#{managedBeanName}")
引用它,则仅在JSF托管的bean内部生成线程才有意义。 您应该确保实现@PreDestroy
以确保所有这些线程在Web应用程序即将关闭时关闭,就像您在ServletContextListener
的contextDestroyed()
方法中执行的那样(是的,您做到了吗?)。 另请参见 是否安全在JSF托管的bean中启动新线程?
java.util.Timer
java.util.Timer
,您绝对不应该使用旧式的Timer
,而是使用现代的ScheduledExecutorService
。 Timer
存在以下主要问题,使其不适用于长时间运行的Java EE Web应用程序(引用自Java Concurrency in Practice):
Timer
对系统时钟的更改非常敏感,而ScheduledExecutorService
则不会。Timer
只有一个执行线程,因此长时间运行的任务可能会延迟其他任务。ScheduledExecutorService
可以配置任意数量的线程。TimerTask
中抛出的任何运行时异常都会杀死该线程,从而使Timer
失效,即预定的任务将不再运行。ScheduledThreadExecutor
不仅捕获运行时异常,而且如果需要,它还允许您处理这些异常。抛出异常的任务将被取消,但其他任务将继续运行。如果您忘记显式地cancel()
Timer
,那么它会在卸载后继续运行。因此,在重新部署后,将创建一个新线程,再次执行相同的任务。等等。现在它已经成为了“fire and forget”,您不能以编程方式取消它。您基本上需要关闭并重新启动整个服务器以清除先前的线程。
如果Timer
线程未标记为守护线程,则它将阻止Web应用程序的卸载和服务器的关闭。您基本上需要强制关闭服务器。其主要缺点是Web应用程序将无法通过例如contextDestroyed()
和@PreDestroy
方法执行优雅的清理。
@Schedule
如果您的目标是Java EE 6或更高版本(例如JBoss AS,GlassFish,TomEE等,因此不是裸骨JSP / Servlet容器,例如Tomcat),请改用@Singleton
EJB 和 @Schedule
方法。这样,容器将自己担心通过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.
}
}
这可以在托管bean中使用@EJB
进行必要的调用:
@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.
}
}
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();
}
}
@Inject
它即可。 - BalusC@EJB
一样),而不是通过引用注入(就像@ManagedProperty
一样)。你只需要确保在正确的时刻访问它即可。如果无法保证,请以另一种方式注入它。 - BalusC