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つのメソッドを複数回スケジュールすることができます。

1.1. Inheritance of metadata

A subclass never inherits the metadata of a @Scheduled method declared on a superclass. For example, suppose the class org.amce.Foo is extended by the class org.amce.Bar. If Foo declares a non-static method annotated with @Scheduled then Bar does not inherit the metadata of the scheduled method. In the following example, the everySecond() method is only invoked upon the instance of Foo.

class Foo {

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

@Singleton
class Bar extends Foo {
}

1.2. CDI events

Some CDI events are fired synchronously and asynchronously when specific events occur.

タイプ Event description

io.quarkus.scheduler.SuccessfulExecution

An execution of a scheduled job completed successfully.

io.quarkus.scheduler.FailedExecution

An execution of a scheduled job completed with an exception.

io.quarkus.scheduler.SkippedExecution

An execution of a scheduled job was skipped.

io.quarkus.scheduler.SchedulerPaused

The scheduler was paused.

io.quarkus.scheduler.SchedulerResumed

The scheduler was resumed.

io.quarkus.scheduler.ScheduledJobPaused

A scheduled job was paused.

io.quarkus.scheduler.ScheduledJobResumed

A scheduled job was resumed.

1.3. トリガー

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

1.3.1. CRON

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

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

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.3.1.1. タイムゾーン

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

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

The timeZone attribute supports Property Expressions including default values and nested Property Expressions.

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

1.3.2. インターバル

An interval trigger defines a period between invocations. The period expression is based on the ISO-8601 duration format PnDTnHnMn.nS and the value of @Scheduled#every() is parsed with java.time.Duration#parse(CharSequence). However, if an expression starts with a digit and ends with d, P prefix will be added automatically. If the expression only starts with a digit, PT prefix is added automatically. So for example, 15m can be used instead of PT15M and is parsed as "15 minutes".

インターバルトリガーの例
@Scheduled(every = "15m")
void every15Mins() { }
A value less than one second may not be supported by the underlying scheduler implementation. In that case a warning message is logged during build and application start.

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.4. アイデンティティ

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

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

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

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

1.5. Delayed Start of a Trigger

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

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

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

@Scheduled#delayed() is a text alternative to the properties above. The period expression is based on the ISO-8601 duration format PnDTnHnMn.nS and the value is parsed with java.time.Duration#parse(CharSequence). However, if an expression starts with a digit and ends with d, P prefix will be added automatically. If the expression only starts with a digit, PT prefix is added automatically. So for example, 15s can be used instead of PT15S and is parsed as "15 seconds".

@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.6. 遅延実行

@Scheduled#executionMaxDelay() can be set to delay each execution of a scheduled method. The value represents the maximum delay between the activation of the trigger and the execution of the scheduled method. The actual delay is a randomized number between 0 and the maximum specified delay.

The value is parsed with DurationConverter#parseDuration(String). It can be a property expression, in which case, the scheduler attempts to use the configured value instead: @Scheduled(executionMaxDelay = "${myJob.maxDelay}"). Additionally, the property expression can specify a default value: @Scheduled(executionMaxDelay = "${myJob.maxDelay}:500ms}").

@Scheduled(every = "2s", executionMaxDelay = "500ms") (1)
void everyTwoSeconds() { }
1 The delay will be a value between 0 and 500 milliseconds. As a result, the period between to everyTwoSeconds() executions will be roughly between one and a half and two and a half seconds.

1.7. 同時実行

デフォルトでは、スケジュールされたメソッドは同時に実行することができます。それにもかかわらず、 @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.8. 条件付き実行

You can define the logic to skip any execution of a scheduled method via @Scheduled#skipExecutionIf(). The specified class must implement io.quarkus.scheduler.Scheduled.SkipPredicate and the execution is skipped if the result of the test() method is true. The class must either represent a CDI bean or declare a public no-args constructor. In case of CDI, there must be exactly one bean that has the specified class in its set of bean types, otherwise the build fails. Furthermore, the scope of the bean must be active during execution of the job. If the scope is @Dependent then the bean instance belongs exclusively to the specific scheduled method and is destroyed when the application is shut down.

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イベントが発生します。
To skip the scheduled executions while the application is starting up/shutting down, you can make use of the io.quarkus.scheduler.Scheduled.ApplicationNotRunning skip predicate.

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

デフォルトでは、スケジュールされたメソッドは、ブロックするタスクのメインエクゼキュータ上で実行されます。そのため、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のイベントループでメソッドを実行するようにスケジューラに指示します。

1.10. How to use multiple scheduler implementations

In some cases, it might be useful to choose a scheduler implementation used to execute a scheduled method. However, only one Scheduler implementation is used for all scheduled methods by default. For example, the quarkus-quartz extension provides an implementation that supports clustering but it also removes the simple in-memory implementation from the game. Now, if clustering is enabled then it’s not possible to define a scheduled method that would be executed locally on a single node. Nevertheless, if you set the quarkus.scheduler.use-composite-scheduler config property to true then a composite Scheduler is used instead. This means that multiple scheduler implementations are kept running side by side. Furthermore, it’s possible to chose a specific implementation used to execute a scheduled method using @Scheduled#executeWith().

class Jobs {

   @Scheduled(cron = "0 15 10 * * ?") (1)
   void fireAt10AmEveryDay() { }

   @Scheduled(every = "1s", executeWith = Scheduled.SIMPLE) (2)
   void everySecond() { }
}
1 If the quarkus-quartz extension is present then this method will be executed with the Quartz-specific scheduler.
2 If quarkus.scheduler.use-composite-scheduler=true is set then this method will be executed with the simple in-memory implementation provided by the quarkus-scheduler extension.

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 を使ってスケジューラを強制的に起動させる必要があるかもしれません。
If the Quartz extension is present and the DB store type is used then it’s not possible to pass a task instance to the job definition and a task class must be used instead. The Quartz API can be also used to schedule a job programmatically.

In certain cases, a more fine-grained approach might be needed which is why Quarkus also exposes java.util.concurrent.ScheduledExecutorService and java.util.concurrent.ExecutorService that can be injected as CDI beans. However, these executors are used by other Quarkus extensions and therefore should be approached with caution. Furthermore, users are never allowed to shut these executors down manually.

class JobScheduler {

   @Inject
   ScheduledExecutorService executor;

   void everySecondWithDelay() {
       Runnable myRunnable = createMyRunnable();
       executor.scheduleAtFixedRate(myRunnable, 3, 1, TimeUnit.SECONDS);
   }
}

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

If quarkus.scheduler.tracing.enabled is set to true and the OpenTelemetry extension is present then every job execution, either defined with the @Scheduled annotation or scheduled programmatically, automatically creates a span named after the job’s アイデンティティ.

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

By default, only one Scheduler implementation is used. If set to true then a composite Scheduler that delegates to all running implementations is used.

Scheduler implementations will be started depending on the value of quarkus.scheduler.start-mode, i.e. the scheduler is not started unless a relevant io.quarkus.scheduler.Scheduled business method is found.

Environment variable: QUARKUS_SCHEDULER_USE_COMPOSITE_SCHEDULER

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.

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

To write duration values, use the standard java.time.Duration format. See the Duration#parse() Java API documentation for more information.

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

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

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

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

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

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

関連コンテンツ