web-dev-qa-db-ja.com

タイマーを使用してスケジュールされたタスクのためにJSF管理対象Beanでスレッドを生成する

アプリケーションスコープBean内でTimerを使用しても問題ないかどうか知りたいのですが。

例として、登録されたすべてのメンバーに1日に1回大量の電子メールを送信するタイマータスクを作成するとします。私はできるだけ多くのJSFを使用しようとしています。これが許容できるかどうか知りたいです(少し奇妙に聞こえます)。

これまで、ServletContextListener内で上記のすべてを使用しました。 (私はアプリケーションサーバーやcronジョブを使用したくないので、上記のものをWebアプリ内に保持したいと考えています。)

これを行うスマートなJSFの方法はありますか、それとも古いパターンに固執する必要がありますか?

28
Dimman

前書き

JSFマネージドBeanの内部からスレッドを生成する場合、_#{managedBeanName}_または他の方法でビュー内でスレッドを参照できるようにする場合、only@ManagedProperty("#{managedBeanName}") による管理対象Bean。 _@PreDestroy_ を実装していることを確認するだけで、 contextDestroyed()ServletContextListener のメソッド(はい、そうしましたか?)参照 JSF管理対象Beanで新しいスレッドを開始しても安全ですか?

Java EEで_Java.util.Timer_を使用しないでください

JSFマネージドBeanで_Java.util.Timer_を使用する場合は、絶対に使用しない昔ながらのTimerを使用する必要がありますが、最新の ScheduledExecutorService を使用してください。 Timerには次のような大きな問題があるため、長時間実行中の使用には適しませんJava EE Webアプリケーション( Java Concurrency in Practice から引用)):

  • Timerはシステムクロックの変化に敏感ですが、ScheduledExecutorServiceは敏感ではありません。
  • Timerには実行スレッドが1つしかないため、実行時間の長いタスクが他のタスクを遅延させる可能性があります。 ScheduledExecutorServiceは、任意の数のスレッドで構成できます。
  • TimerTaskでスローされた実行時例外は、その1つのスレッドを強制終了するため、Timerが無効になります。つまり、スケジュールされたタスクが実行されなくなります。 ScheduledThreadExecutorは、ランタイム例外をキャッチするだけでなく、必要に応じてそれらを処理できます。例外をスローしたタスクはキャンセルされますが、他のタスクは引き続き実行されます。

本の引用とは別に、私はより多くの欠点を考えることができます:

  • Timerを明示的にcancel()するのを忘れた場合、アンデプロイ後も実行され続けます。したがって、再デプロイ後、新しいスレッドが作成され、同じジョブが再度実行されます。等。これは「ファイアアンドフォーゲット」になり、プログラムでキャンセルすることはできなくなりました。以前のスレッドをクリアするには、基本的にサーバー全体をシャットダウンして再起動する必要があります。

  • Timerスレッドがデーモンスレッドとしてマークされていない場合、Webアプリケーションのアンデプロイとサーバーのシャットダウンがブロックされます。基本的にサーバーを強制終了する必要があります。主な欠点は、ウェブアプリが次のような方法で適切なクリーンアップを実行できないことです。 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.
    }

} 
_

これは、必要に応じて _@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();
    }

}
_
70
BalusC