The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

スケジューラーリファレンスガイド

最近のアプリケーションでは、特定のタスクを定期的に実行する必要があることがよくあります。Quarkusには、2つのスケジューラエクステンションがあります。 quarkus-scheduler エクステンションは、APIと軽量のインメモリスケジューラー実装を提供します。 quarkus-quartz エクステンションは、 quarkus-scheduler エクステンションのAPIを実装し、Quartzライブラリに基づくスケジューラ実装を含んでいます。 quarkus-quartz が必要になるのは、永続タスクやクラスタリングなど、より高度なスケジューリングのユースケースだけです。

プロジェクトに quarkus-quartz 依存関係を追加すると、 quarkus-scheduler エクステンションからの軽量スケジューラーの実装は自動的に無効になります。

1. スケジュールされたメソッド

@io.quarkus.scheduler.Scheduled でアノテーションされたメソッドは、自動的に呼び出しがスケジュールされます。スケジュールされたメソッドは、abstractやprivateであってはなりません。静的または非静的であっても大丈夫です。スケジュールされたメソッドには、 @jakarta.transaction.Transactional@org.eclipse.microprofile.metrics.annotation.Counted のようなインターセプターバインディングをアノテーションすることができます。

スコープを持たないBeanクラスで、 @Scheduled でアノテーションされた非静的メソッドが少なくとも一つ宣言されている場合、 @Singleton が使用されます。

さらに、アノテーションされたメソッドは void を返し、パラメーターを宣言しないか、 io.quarkus.scheduler.ScheduledExecution 型のパラメーターを 1 つだけ宣言する必要があります。

アノテーションは繰り返し可能なので、1つのメソッドを複数回スケジュールすることができます。

サブクラスは、スーパークラスで宣言された @Scheduled メソッドのメタデータを継承することはありません。次の例では、 everySecond() メソッドは Jobs のインスタンスに対してのみ呼び出されます。

class Jobs {

   @Scheduled(every = "1s")
   void everySecond() {
     // ..do something
   }
}

@Singleton
class MyJobs extends Jobs {
}

io.quarkus.scheduler.SuccessfulExecution 型の CDI イベントは、スケジュールされたメソッドの実行が成功したときに、同期および非同期で発生します。 io.quarkus.scheduler.FailedExecution タイプの CDI イベントは、スケジュールされたメソッドの実行が例外をスローしたときに、同期および非同期で発生します。

1.1. トリガー

トリガーは、 @Scheduled#cron() 属性または @Scheduled#every() 属性によって定義されます。両方が指定されている場合、cron 式が優先されます。何も指定されていない場合、ビルドは IllegalStateException で失敗します。

1.1.1. CRON

CRONトリガーは、cronライクな式で定義されています。例えば "0 15 10 * * ?" は毎日午前10時15分に起動します。

CRONトリガーの例
@Scheduled(cron = "0 15 10 * * ?")
void fireAt10AmEveryDay() { }

CRON 式で使用される構文は quarkus.scheduler.cron-type プロパティーによって制御されます。 値は cron4j , quartz , unix および spring のいづれかです。 quartz がデフォルトで使用されます。

cron 属性は、デフォルト値やネストされたプロパティ式を含む プロパティ式 をサポートしています。(なお、"{property.path}"スタイルの表現もサポートされていますが、プロパティ式の完全な機能は提供されていません)。

CRON設定プロパティーの例
@Scheduled(cron = "${myMethod.cron.expr}")
void myMethod() { }

特定のスケジュールメソッドを無効にしたい場合は、そのcron式を "off" または "disabled" に設定します。

application.properties
myMethod.cron.expr=disabled

プロパティ式では、そのプロパティが構成されていない場合に使用されるデフォルト値を定義することができます。

デフォルト値が 0 0 15 ? * MON * のCRON設定プロパティーの例
@Scheduled(cron = "${myMethod.cron.expr:0 0 15 ? * MON *}")
void myMethod() { }

プロパティ myMethod.cron.expr が未定義または null の場合は、デフォルト値( 0 0 15 ? * MON * )が使用されます。

1.1.1.1. タイムゾーン

cron式は、デフォルトのタイムゾーンのコンテキストで評価されます。しかし、cron式を特定のタイムゾーンに関連付けることも可能です。

タイムゾーンの例
@Scheduled(cron = "0 15 10 * * ?", timeZone = "Europe/Prague") (1)
void myMethod() { }
1 タイムゾーンIDは、 java.time.ZoneId#of(String) を使って解析されます。

timeZone 属性はデフォルト値とネストされた値のプロパティー式を含む Property Expressions をサポートします。

タイムゾーン設定プロパティの例
@Scheduled(cron = "0 15 10 * * ?", timeZone = "{myMethod.timeZone}")
void myMethod() { }

1.1.2. インターバル

インターバルトリガーは、呼び出しの間の期間を定義します。期間式は ISO-8601 の期間フォーマット PnDTnHnMn.nS に基づいており、 @Scheduled#every() の値は java.time.Duration#parse(CharSequence) で解析されます。ただし、式が数字で始まる場合は、 PT の接頭辞が自動的に追加されます。例えば、 PT15M の代わりに 15m を使用することができ、「15 分」と解析されます。

インターバルトリガーの例
@Scheduled(every = "15m")
void every15Mins() { }
1 秒未満の値は、基盤となるスケジューラー実装ではサポートされていない可能性があります。その場合、ビルドおよびアプリケーションの起動時に警告メッセージが記録されます。

every 属性は、デフォルト値やネストされたプロパティ式を含む プロパティ式 をサポートしています。(なお、 "{property.path}" スタイル式はサポートされていますが、プロパティ式の完全な機能は提供されていません)。

インターバル設定プロパティーの例
@Scheduled(every = "${myMethod.every.expr}")
void myMethod() { }

Intervals は、その値を "off" または "disabled" に設定することで無効にすることができます。そのため、例えば、デフォルト値 "off" のプロパティ式は、そのConfig Propertyが設定されていない場合、トリガーを無効にするために使用することができます。

デフォルト値のあるインターバル設定プロパティーの例
@Scheduled(every = "${myMethod.every.expr:off}")
void myMethod() { }

1.2. アイデンティティ

デフォルトでは、スケジュールされた各メソッドに対して一意の ID が生成されます。このIDはログメッセージやデバッグ中に使用されます。時には、明示的な id を指定することが便利な場合もあります。

アイデンティティの例
@Scheduled(identity = "myScheduledMethod")
void myMethod() { }

identity 属性は、デフォルト値やネストされたプロパティ式を含む プロパティ式をサポートしています。(なお、 "{property.path}" スタイル式はサポートされていますが、プロパティ式の完全な機能は提供されていません)。

インターバル設定プロパティーの例
@Scheduled(identity = "${myMethod.identity.expr}")
void myMethod() { }

1.3. 遅延実行

@Scheduled では、トリガーが発報を開始時刻を遅らせるための2つの方法を提供しています。

@Scheduled#delay()@Scheduled#delayUnit() は初期遅延を一緒に形成します。

@Scheduled(every = "2s", delay = 2, delayUnit = TimeUnit.HOUR) (1)
void everyTwoSeconds() { }
1 アプリケーション開始から2時間後に初めてトリガーが発射されます。
最終的な値は常に1秒単位で丸められます。

@Scheduled#delayed() は、上記のプロパティーのテキスト形式での代替です。ピリオド式は ISO-8601 duration format PnDTnHnMn.nS に基づいており、値は java.time.Duration#parse(CharSequence) で解析されます。ただし、式が数字で始まる場合は、 PT の接頭辞が自動的に追加されます。そのため、例えば PT15S の代わりに 15s を使用することができ、「15 秒」と解析されます。

@Scheduled(every = "2s", delayed = "2h")
void everyTwoSeconds() { }
@Scheduled#delay() がゼロよりも大きい値に設定されている場合、 @Scheduled#delayed() の値は無視されます。

@Scheduled#delay() と比較した場合の主な利点は、値が設定可能であることです。 delay 属性は、デフォルト値やネストされたプロパティ式を含む プロパティ式をサポートしています。(なお、 "{property.path}" スタイルの表現はサポートされていますが、プロパティ式の完全な機能は提供されていません)。

@Scheduled(every = "2s", delayed = "${myMethod.delay.expr}") (1)
void everyTwoSeconds() { }
1 遅延の設定には、configプロパティー myMethod.delay.expr を使用します。

1.4. 同時実行

デフォルトでは、スケジュールされたメソッドは同時に実行することができます。それにもかかわらず、 @Scheduled#concurrentExecution() を通じて同時実行を処理するための戦略を指定することが可能です。

import static io.quarkus.scheduler.Scheduled.ConcurrentExecution.SKIP;

@Scheduled(every = "1s", concurrentExecution = SKIP) (1)
void nonConcurrent() {
  // we can be sure that this method is never executed concurrently
}
1 同時実行はスキップされます。
スケジュールされたメソッドの実行がスキップされると、 io.quarkus.scheduler.SkippedExecution のタイプのCDIイベントが発生します。
同じアプリケーションインスタンス内での実行のみが考慮されることに注意してください。この機能は、クラスター間での動作を意図したものではありません。

1.5. 条件付き実行

@Scheduled#skipExecutionIf() を使用して、スケジュールされたメソッドの実行をスキップするロジックを定義できます。 指定されたクラスは io.quarkus.scheduler.Scheduled.SkipPredicate を実装する必要があり、 test() メソッドの結果が true の場合、実行はスキップされます。 クラスは CDI Bean を表すか、引数なしのパブリックコンストラクターを宣言する必要があります。 CDI の場合、Bean タイプのセット内に指定されたクラスを持つ Bean が 1 つだけ存在する必要があります。そうでない場合、ビルドは失敗します。 さらに、ジョブの実行中は Bean のスコープがアクティブである必要があります。 スコープが @Dependent の場合、Bean インスタンスは特定のスケジュールメソッドだけに属し、アプリケーションがシャットダウンされると破棄されます。

class Jobs {

   @Scheduled(every = "1s", skipExecutionIf = MyPredicate.class) (1)
   void everySecond() {
     // do something every second...
   }
}

@Singleton (2)
class MyPredicate implements SkipPredicate {

   @Inject
   MyService service;

   boolean test(ScheduledExecution execution) {
       return !service.isStarted(); (3)
   }
}
1 MyPredicate.class の Bean インスタンスは、実行をスキップすべきかどうかを評価するために使用されます。Beanタイプのセットに指定されたクラスを持つBeanが正確に1つ存在する必要があり、そうでない場合はビルドが失敗します。
2 Beanのスコープは、実行中を通じてアクティブです。
3 Jobs.everySecond() は、 MyService.isStarted()true を返すまでスキップされます。

なお、これは以下のコードと同等のものです:

class Jobs {

   @Inject
   MyService service;

   @Scheduled(every = "1s")
   void everySecond() {
     if (service.isStarted()) {
        // do something every second...
     }
   }
}

主なアイデアは、スケジュールされたビジネスメソッドの外で実行をスキップするロジックを維持することで、再利用やリファクタリングが容易にできるようにすることです。

スケジュールされたメソッドの実行がスキップされると、 io.quarkus.scheduler.SkippedExecution のタイプのCDIイベントが発生します。
アプリケーションの起動/シャットダウン中にスケジュールされた実行をスキップするには、 io.quarkus.scheduler.Scheduled.ApplicationNotRunning のスキップ条件を使用できます。

1.6. ノンブロッキング方式

デフォルトでは、スケジュールされたメソッドは、ブロッキングタスク用のメインエクゼキュータ上で実行されます。そのため、Vert.xイベントループ上で実行するように設計された技術(Hibernate Reactiveなど)は、メソッド本体内で使用することができません。このため、 java.util.concurrent.CompletionStage<Void> または io.smallrye.mutiny.Uni<Void> を返す、あるいは @io.smallrye.common.annotation.NonBlocking でアノテーションされたスケジュール型メソッドは、代わりに Vert.x のイベントループで実行されます。

class Jobs {

   @Scheduled(every = "1s")
   Uni<Void> everySecond() { (1)
     // ...do something async
   }
}
1 リターンタイプ Uni<Void> は、Vert.xのイベントループでメソッドを実行するようにスケジューラに指示します。

2. スケジューラー

Quarkusは、 io.quarkus.scheduler.Scheduler 型のビルトインBeanを提供しており、これを注入してスケジューラーを一時停止/再開するために使用することができます。

スケジューラーインジェクションの例
import io.quarkus.scheduler.Scheduler;

class MyService {

   @Inject
   Scheduler scheduler;

   void ping() {
      scheduler.pause(); (1)
      scheduler.pause("myIdentity"); (2)
      if (scheduler.isRunning()) {
         throw new IllegalStateException("This should never happen!");
      }
      scheduler.resume("myIdentity"); (3)
      scheduler.resume(); (4)
      scheduler.getScheduledJobs(); (5)
      Trigger jobTrigger = scheduler.getScheduledJob("myIdentity"); (6)
      if (jobTrigger != null && jobTrigger.isOverdue()){ (7)
        // the job is late to the party.
      }
   }
}
1 すべてのトリガーを一時停止します。
2 スケジュールされた特定のメソッドをそのIDで一時停止する
3 スケジュールされた特定のメソッドを、そのIDで再開する
4 スケジューラーを再開します。
5 スケジューラーを再開します。
6 スケジュールされた特定のメソッドを、そのIDで再開する。
7 isOverdue()の猶予期間は、quarkus.scheduler.overdue-grace-periodを使用して設定することができます
CDIイベントは、スケジューラーやスケジュールされたジョブが一時停止/再開されたときに、同期および非同期で発生します。ペイロードはそれぞれ io.quarkus.scheduler.SchedulerPaused , io.quarkus.scheduler.SchedulerResumed , io.quarkus.scheduler.ScheduledJobPaused , io.quarkus.scheduler.ScheduledJobResumed です。

3. プログラムスケジューリング

注入された io.quarkus.scheduler.Scheduler は、プログラム的にジョブをスケジュールするために使用することもできます。

プログラムスケジューリング
import io.quarkus.scheduler.Scheduler;

@ApplicationScoped
class MyJobs {

    @Inject
    Scheduler scheduler;

    void addMyJob() { (1)
        scheduler.newJob("myJob")
            .setCron("0/5 * * * * ?")
            .setTask(executionContext -> { (2)
                // do something important every 5 seconds
            })
            .schedule(); (3)
    }

    void removeMyJob() {
        scheduler.unscheduleJob("myJob"); (4)
    }
}
1 これは、 @Scheduled(identity = "myJob", cron = "0/5 * * * * ?") でアノテーションされたメソッドに代わるプログラム的な代替です。
2 ビジネスロジックはコールバックで定義されます。
3 JobDefinition#schedule() メソッドが呼び出されると、ジョブがスケジュールされます。
4 プログラムによって追加されたジョブも削除することができます。
デフォルトでは、 @Scheduled ビジネスメソッドが見つからない限り、スケジューラは開始されません。「純粋な」プログラムによるスケジューリングでは、 quarkus.scheduler.start-mode=forced を使ってスケジューラを強制的に起動させる必要があるかもしれません。
Quartz エクステンション が存在し、DB ストアタイプが使用されている場合、タスクインスタンスをジョブ定義に渡すことはできず、代わりにタスククラスを使用する必要があります。Quartz API を使用して、プログラムでジョブをスケジュールすることもできます。

4. スケジュールされたメソッドとテスト

テストを実行する際には、スケジューラーを無効にすることが望ましいことがよくあります。スケジューラーは、ランタイム設定プロパティー quarkus.scheduler.enabled を通じて無効にすることができます。 false に設定すると、アプリケーションにスケジュールされたメソッドが含まれていても、 スケジューラーは起動しません。特定の テストプロファイル に対してスケジューラーを無効にすることもできます。

5. メトリクス

quarkus.scheduler.metrics.enabledtrue に設定されていて、metrics エクステンションが存在する場合、いくつかの基本的なメトリクスがすぐに公開されます。

Micrometer エクステンション が存在する場合、すべての @Scheduled メソッドに @io.micrometer.core.annotation.Timed インターセプター・バインディングが自動的に追加され (すでに存在する場合を除く)、 scheduled.methods という名前の io.micrometer.core.instrument.Timerscheduled.methods.running という名前の io.micrometer.core.instrument.LongTaskTimer が登録されます。宣言するクラスの修飾子名と @Scheduled メソッド名がタグとして使用されます。

SmallRye Metrics エクステンションが存在する場合、 @org.eclipse.microprofile.metrics.annotation.Timed インターセプターバインディングがすべての @Scheduled メソッドに自動的に追加され(すでに存在している場合を除く)、 org.eclipse.microprofile.metrics.Timer が各 @Scheduled メソッドに対して作成されます。名前は、宣言したクラスの完全修飾名と @Scheduled メソッドの名前で構成されます。タイマーには、タグ scheduled=true が付いています。

6. OpenTelemetry Tracing

quarkus.scheduler.tracing.enabledtrue に設定し、OpenTelemetry エクステンション が存在する場合、 @Scheduled アノテーションで定義されているか、プログラムでスケジュールされているかに関係なく、ジョブの アイデンティティ をもとにした名前でスパンが自動的に作成されます。

7. 仮想スレッド上で @Scheduled メソッドを実行

@Scheduled でアノテーションされたメソッドは、 @RunOnVirtualThread でアノテーションすることもできます。 この場合、メソッドは仮想スレッド上で呼び出されます。

メソッドは void を返す必要があり、Java ランタイムは仮想スレッドのサポートを提供する必要があります。 詳細については、 仮想スレッドガイド をお読みください。

8. 設定リファレンス

ビルド時に固定される設定プロパティ - 他のすべての設定プロパティは実行時にオーバーライド可能

Configuration property

タイプ

デフォルト

The syntax used in CRON expressions.

Environment variable: QUARKUS_SCHEDULER_CRON_TYPE

Show more

cron4j, quartz, unix, spring, spring53

quartz

Scheduled task metrics will be enabled if a metrics extension is present and this value is true.

Environment variable: QUARKUS_SCHEDULER_METRICS_ENABLED

Show more

boolean

false

Controls whether tracing is enabled. If set to true and the OpenTelemetry extension is present, tracing will be enabled, creating automatic Spans for each scheduled task.

Environment variable: QUARKUS_SCHEDULER_TRACING_ENABLED

Show more

boolean

false

If schedulers are enabled.

Environment variable: QUARKUS_SCHEDULER_ENABLED

Show more

boolean

true

Scheduled task will be flagged as overdue if next execution time is exceeded by this period.

Environment variable: QUARKUS_SCHEDULER_OVERDUE_GRACE_PERIOD

Show more

Duration

1S

Scheduler can be started in different modes. By default, the scheduler is not started unless a io.quarkus.scheduler.Scheduled business method is found.

Environment variable: QUARKUS_SCHEDULER_START_MODE

Show more

normalThe scheduler is not started unless a io.quarkus.scheduler.Scheduled business method is found., forcedThe scheduler will be started even if no scheduled business methods are found. This is necessary for "pure" programmatic scheduling., haltedJust like the forced mode but the scheduler will not start triggering jobs until Scheduler#resume() is called. This can be useful to run some initialization logic that needs to be performed before the scheduler starts.

期間フォーマットについて

期間の値を書くには、標準の java.time.Duration フォーマットを使います。 詳細は Duration#parse() Java API documentation を参照してください。

数字で始まる簡略化した書式を使うこともできます:

  • 数値のみの場合は、秒単位の時間を表します。

  • 数値の後に ms が続く場合は、ミリ秒単位の時間を表します。

その他の場合は、簡略化されたフォーマットが解析のために java.time.Duration フォーマットに変換されます:

  • 数値の後に hms が続く場合は、その前に PT が付けられます。

  • 数値の後に d が続く場合は、その前に P が付けられます。

関連コンテンツ