アプリケーションスコープBean内でTimer
を使用しても問題ないかどうか知りたいのですが。
例として、登録されたすべてのメンバーに1日に1回大量の電子メールを送信するタイマータスクを作成するとします。私はできるだけ多くのJSFを使用しようとしています。これが許容できるかどうか知りたいです(少し奇妙に聞こえます)。
これまで、ServletContextListener
内で上記のすべてを使用しました。 (私はアプリケーションサーバーやcronジョブを使用したくないので、上記のものをWebアプリ内に保持したいと考えています。)
これを行うスマートなJSFの方法はありますか、それとも古いパターンに固執する必要がありますか?
JSFマネージドBeanの内部からスレッドを生成する場合、_#{managedBeanName}
_または他の方法でビュー内でスレッドを参照できるようにする場合、only@ManagedProperty("#{managedBeanName}")
による管理対象Bean。 _@PreDestroy
_ を実装していることを確認するだけで、 contextDestroyed()
ServletContextListener
のメソッド(はい、そうしましたか?)参照 JSF管理対象Beanで新しいスレッドを開始しても安全ですか?
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
_メソッド。
@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;
_
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();
}
}
_