Java EEでのスケジューリング
1. 概要
以前のarticleでは、* @Scheduled *__アノテーションを使用してSpringでタスクをスケジュールする方法を示しました。 この記事では、前の記事で示した各ケースでthe timer service in a Java Enterprise Edition applicationを使用して、同じことを実現する方法を示します。
2. スケジューリングのサポートを有効にする
Java EEアプリケーションでは、時間指定タスクのサポートを有効にする必要はありません。 タイマーサービスは、アプリケーションが時間ベースのイベント用にスケジュールされたメソッドを呼び出すことを可能にするコンテナ管理サービスです。 例として、統計を生成するために、アプリケーションが特定の時間にいくつかの日次レポートを実行する必要がある場合があります。
タイマーには2つのタイプがあります。
-
Programmatic timers:タイマーサービスは任意のBean(ステートフルセッションBeanを除く)に注入でき、ビジネスロジックは@Timeoutアノテーションが付けられたメソッドに配置する必要があります。 タイマーは、Beanの@PostConstructアノテーションが付けられたメソッドによって初期化することも、メソッドを呼び出すだけで初期化することもできます。
-
Automatic timers:ビジネスロジックは、@Scheduleまたは@Schedulesで注釈が付けられた任意のメソッドに配置されます。 これらのタイマーは、アプリケーションが起動するとすぐに初期化されます。
それでは、最初の例から始めましょう。
3. 固定遅延を使用したタスクのスケジュール
Springでは、これは@Scheduled(fixedDelay = 1000)アノテーションを使用するだけで実行されます。 この場合、最後の実行の終了から次の実行の開始までの期間は固定されます。 タスクは、前のタスクが完了するまで常に待機します。
Java EEでまったく同じことを行うのは、似たような組み込みのメカニズムが提供されていないため、達成するのが少し難しくなります。 これがどのように行われるかを見てみましょう。
@Singleton
public class FixedTimerBean {
@EJB
private WorkerBean workerBean;
@Lock(LockType.READ)
@Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
public void atSchedule() throws InterruptedException {
workerBean.doTimerWork();
}
}
@Singleton
public class WorkerBean {
private AtomicBoolean busy = new AtomicBoolean(false);
@Lock(LockType.READ)
public void doTimerWork() throws InterruptedException {
if (!busy.compareAndSet(false, true)) {
return;
}
try {
Thread.sleep(20000L);
} finally {
busy.set(false);
}
}
}
ご覧のとおり、タイマーは5秒ごとにトリガーされるようにスケジュールされています。 ただし、この場合にトリガーされたメソッドは、現在のThreadでsleep()を呼び出すことにより、20秒の応答時間をシミュレートしました。
結果として、コンテナは5秒ごとにdoTimerWork()を呼び出し続けますが、メソッドの先頭に置かれた条件であるbusy.compareAndSet(false, true),は、前の呼び出しが終了していなければすぐに戻ります。 これでは、前のタスクが終了した後にのみ次のタスクが実行されるようにします。
4. 固定レートでタスクをスケジュールする
これを行う1つの方法は、@Resourceを使用して注入され、@PostConstructという注釈が付けられたメソッドで構成されたtimer serviceを使用することです。 タイマーの期限が切れると、@Timeoutアノテーションが付けられたメソッドが呼び出されます。
前の記事で述べたように、タスクの実行の開始は、前の実行の完了を待ちません。 このオプションは、タスクの各実行が独立している場合に使用する必要があります。 次のコードスニペットは、毎秒起動するタイマーを作成します。
@Startup
@Singleton
public class ProgrammaticAtFixedRateTimerBean {
@Inject
Event event;
@Resource
TimerService timerService;
@PostConstruct
public void initialize() {
timerService.createTimer(0,1000, "Every second timer with no delay");
}
@Timeout
public void programmaticTimout(Timer timer) {
event.fire(new TimerEvent(timer.getInfo().toString()));
}
}
別の方法は、@Scheduledアノテーションを使用することです。 次のコードスニペットでは、5秒ごとにタイマーを起動します。
@Startup
@Singleton
public class ScheduleTimerBean {
@Inject
Event event;
@Schedule(hour = "*", minute = "*", second = "*/5", info = "Every 5 seconds timer")
public void automaticallyScheduled(Timer timer) {
fireEvent(timer);
}
private void fireEvent(Timer timer) {
event.fire(new TimerEvent(timer.getInfo().toString()));
}
}
5. 初期遅延を伴うタスクのスケジュール
ユースケースシナリオでタイマーを遅延で開始する必要がある場合は、それも可能です。 この場合、Java EEではtimer serviceの使用が許可されます。 タイマーの初期遅延が10秒で、その後5秒ごとに起動する例を見てみましょう。
@Startup
@Singleton
public class ProgrammaticWithInitialFixedDelayTimerBean {
@Inject
Event event;
@Resource
TimerService timerService;
@PostConstruct
public void initialize() {
timerService.createTimer(10000, 5000, "Delay 10 seconds then every 5 seconds timer");
}
@Timeout
public void programmaticTimout(Timer timer) {
event.fire(new TimerEvent(timer.getInfo().toString()));
}
}
サンプルで使用されているcreateTimerメソッドは、次のシグネチャcreateTimer(long initialDuration, long intervalDuration, java.io.Serializable info)を使用しています。ここで、initialDurationは最初のタイマー満了通知までに経過する必要のあるミリ秒数であり、intervalDurationはタイマーの有効期限通知の間に経過する必要があるミリ秒数。
この例では、10秒のinitialDurationと5秒のintervalDurationを使用しています。 タスクはinitialDuration値の後に初めて実行され、intervalDurationに従って実行され続けます。
6. Cron式を使用したタスクのスケジュール
私たちが見たすべてのスケジューラーは、プログラムと自動の両方で、cron式を使用できます。 例を見てみましょう:
@Schedules ({
@Schedule(dayOfMonth="Last"),
@Schedule(dayOfWeek="Fri", hour="23")
})
public void doPeriodicCleanup() { ... }
この例では、メソッドdoPeriodicCleanup()は、毎週金曜日の23:00と月末に呼び出されます。
7. 結論
この記事では、Springを使用してサンプルが作成された前の記事を開始点として使用して、schedule tasks in the Java EE environmentのさまざまな方法を検討しました。
コードサンプルはGitHub repositoryにあります。