使用计时器在 JSF 托管 bean 中为计划任务生成线程
Posted
技术标签:
【中文标题】使用计时器在 JSF 托管 bean 中为计划任务生成线程【英文标题】:Spawning threads in a JSF managed bean for scheduled tasks using a timer 【发布时间】:2011-11-21 21:47:11 【问题描述】:我想知道在应用程序范围的 bean 中使用 Timer
是否可以。
例如,假设我想创建一个计时器任务,每天一次向每个注册会员发送一堆电子邮件。我正在尝试尽可能多地使用 JSF,我想知道这是否可以接受(我知道这听起来有点奇怪)。
到目前为止,我已经在 ServletContextListener
中使用了以上所有内容。 (我不想使用任何应用程序服务器或 cron 作业,我想保留
我的网络应用程序中的上述内容。)
有没有一种聪明的 JSF 方法可以做到这一点,还是我应该坚持旧模式?
【问题讨论】:
【参考方案1】:简介
至于从 JSF 托管 bean 内部生成线程,只有如果您希望能够通过 #managedBeanName
在您的视图中引用它或通过 @ 在其他托管 bean 中引用它,这将是有意义的987654321@。您只应确保实现 @PreDestroy
以确保在 web 应用程序即将关闭时关闭所有这些线程,就像您在 ServletContextListener
的 contextDestroyed()
方法中所做的那样(是的,您这样做了?)。另见Is it safe to start a new thread in a JSF managed bean?
切勿在 Java EE 中使用 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
方法。
EJB 可用吗?使用@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.
如有必要,@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();
【讨论】:
我记得使用 contextDestroyed() :P 注意到 ScheduledExecutorService。我只需要它来显示上次计划执行的时间并杀死/重新运行它。再次感谢! 我改进了反对Timer
的论点的答案。我真的会重新考虑使用ScheduledExecutorService
。
@BalusC 如果需要从调度程序引用另一个 CDI bean 怎么办? - 什么方法最好用? - 谢谢。
@timbooo:@Inject
照常使用。
@BalusC 但是不可能将会话范围的 bean 注入到 ejb Singleton 中,因为没有活动的会话上下文。有什么可能的解决方法?以上是关于使用计时器在 JSF 托管 bean 中为计划任务生成线程的主要内容,如果未能解决你的问题,请参考以下文章