Нерезидентные потоки в JSF, управляемые bean для запланированных задач с использованием таймера

Я хотел бы знать, можно ли использовать Timer внутри приложения с областью beans.

Например, скажем, что я хочу создать задачу таймера, которая отправляет каждую электронную почту каждому зарегистрированному члену один раз в день. Я пытаюсь использовать как можно больше JSF, и я хотел бы знать, приемлемо ли это (это кажется немного странным, я знаю).

До сих пор я использовал все вышеперечисленное внутри a ServletContextListener. (Я не хочу использовать какой-либо сервер приложений или работу cron, и я хочу сохранить выше всего в моем веб-приложении.)

Есть ли способ JSF для смартфонов, или я должен придерживаться старого шаблона?

Ответ 1

Введение

Что касается нереста потока изнутри управляемого JSF bean, было бы разумно только, если вы хотите иметь возможность ссылаться на него в своих представлениях на #{managedBeanName} или в другом управляемом beans на @ManagedProperty("#{managedBeanName}"). Вы должны только убедиться, что вы реализуете @PreDestroy, чтобы гарантировать, что все эти потоки будут отключены всякий раз, когда webapp будет закрыт, например, как вы в contextDestroyed() метод ServletContextListener ( да вы сделали?). См. Также Безопасно ли запустить новый поток в управляемом JSF bean?

Никогда не используйте java.util.Timer в Java EE

Что касается использования java.util.Timer в управляемом JSF bean, вы должны абсолютно не использовать старомодный Timer, но современный ScheduledExecutorService. Timer имеет следующие основные проблемы, что делает его непригодным для использования в веб-приложении Java EE с длительным сроком действия (цитируется Java Concurrency in Practice):

  • Timer чувствителен к изменениям в системных часах, ScheduledExecutorService не является.
  • Timer имеет только один поток выполнения, поэтому длительная задача может задержать другие задачи. ScheduledExecutorService можно настроить с любым количеством потоков.
  • Любые исключения во время выполнения, заброшенные в TimerTask, убивают один поток, тем самым делая Timer мертвым, т.е. запланированные задачи больше не будут выполняться. ScheduledThreadExecutor не только улавливает исключения во время выполнения, но позволяет вам обрабатывать их, если хотите. Задача, которая выбрала исключение, будет отменена, но другие задачи будут продолжать выполняться.

Помимо цитат из книги, я могу придумать больше недостатков:

  • Если вы забыли явно cancel() Timer, то он продолжает работать после undeployment. Поэтому после повторного развертывания создается новый поток, снова выполняющий ту же работу. Etcetera. К настоящему времени он стал "огнем и забыть", и вы больше не можете его программно отменить. В основном вам нужно отключить и перезагрузить весь сервер, чтобы очистить предыдущие потоки.

  • Если поток Timer не помечен как поток демона, он блокирует unappocation webapp и завершение работы сервера. Вам в основном нужно будет сильно убить сервер. Основным недостатком является то, что webapp не сможет выполнять изящную очистку, например, contextDestroyed() и @PreDestroy.

Доступен EJB? Используйте @Schedule

Если вы нацелились на Java EE 6 или новее (например, JBoss AS, GlassFish, TomEE и т.д. и, следовательно, не бобовидный JSP/сервлет-контейнер, такой как 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.
    }

} 

Это, если необходимо, доступно в управляемом beans с помощью @EJB:

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

}

Если вам вообще не нужно ссылаться на него или на другое управляемое beans, тогда лучше просто использовать 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();
    }

}