現在作っているもので、タスクの開始時間を柔軟に行えないか調べていたら、普通に Spring のドキュメントに書いてあり、実験の結果をメモした。
なお、ここに書いてることは多くのケースでは使わないし普通のスケジューリングで十分だと思っている。また、そうでなくても Spring Retry でまかなえる可能性が高いので、普通には覚える必要はないかと思われる。もし使う必要があるのなら設計を疑ったほうがいい(鏡)。
Spring (3.0 から) では TaskScheduler
というインターフェースが提供されていて、これに具体的な日時と Runnable
を指定してタスクを実行させられる。この TaskScheduler
のメソッドに次のようなメソッドがある。
ScheduledFuture schedule(Runnable task, Trigger trigger)
これは Trigger
というオブジェクトによって、実行開始時間を計算しながらタスクのスケジューリングを行うメソッドとなっている。
Trigger
は次のようなインターフェースで、これとタスク(Runnable
)を TaskScheduler
にわたすとタスクのスケジューリングが登録できる。
interface Trigger {
Date nextExecutionTime(TriggerContext trigggerContext);
}
Trigger
には CronTrigger
と PeriodicTrigger
があり、 @Scheduled
アノテーションでのクーロン指定、およびピリオド指定にそれぞれ対応している。ここではカスタムのスケジューリングを行いたいので、 Trigger
タスクを実装してみる
例えば次のようなスケジューリングを行いたいとする
- 前回のタスクが 5 分よりもかかった場合は、次のタスクは 2 分後に開始する
- 前回のタスクが 2 分よりもかかった場合は、次のタスクは 3 分後に開始する
- 前回のタスクが 1 分よりもかかった場合は、次のタスクは 6 分後に開始する
- 前回のタスクが 1 分以下だった場合は、次のタスクは 8 分後に開始する
- 初回起動の場合は、次のタスクは 1 分後に開始する
これを満たす Trigger
を記述すると次のようになる
object MyTrigger: Trigger { override fun nextExecutionTime(triggerContext: TriggerContext): Date = triggerContext.lastActualExecutionTime().with { triggerContext.lastCompletionTime() } ?.map { start, end -> PreviousTask(end.toInstant(),duration(start, end)) } ?.determineConditionally<PreviousTask, Instant>() ?.on { FIVE_MIN < it.timeElapsed }?.then { it.finishedAt + TWO_MIN } ?.on { TWO_MIN < it.timeElapsed }?.then { it.finishedAt + THREE_MIN } ?.on { ONE_MIN < it.timeElapsed }?.then { it.finishedAt + SIX_MIN } ?.others { it.finishedAt + EIGHT_MIN } ?.date ?: (Instant.now() + ONE_MIN).date private val FIVE_MIN: Duration = Duration.ofSeconds(5 * DurableTask.lengthUnit) private val TWO_MIN: Duration = Duration.ofSeconds(2 * DurableTask.lengthUnit) private val ONE_MIN: Duration = Duration.ofSeconds(1 * DurableTask.lengthUnit) // 以下省略 }
簡単に言えば、次にタスクを起動する時刻を表す Date
オブジェクトを返せば良い
これを用いて タスクをスケジュールするには次のようなコードを実行する
@Component class Task(private val scheduler: TaskScheduler) { override fun run(args: Array<String>) { scheduler.schedule(Runnable { heavyTask() }, MyTrigger) } }
実際お実験コードはこちら