Порождение потоков в управляемом компоненте JSF для запланированных задач с использованием таймера - PullRequest
28 голосов
/ 21 сентября 2011

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

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

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

Есть ли умный способ JSF сделать это или я должен придерживаться старогорисунок

1 Ответ

70 голосов
/ 21 сентября 2011

Введение

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

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

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

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

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

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

  • Если поток Timer не помечен как поток демона, он заблокирует отключение веб-приложения и остановку сервера.Вам в основном нужно жестко убить сервер.Основным недостатком является то, что веб-приложение не сможет выполнить изящную очистку, например, методами 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:

@EJB
private BackgroundJobManager backgroundJobManager;

EJB недоступен?Используйте ScheduledExecutorService

Без EJB вам придется вручную работать с ScheduledExecutorService.Реализация управляемого компонента в области приложения будет выглядеть примерно так:

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

}
...