タスクの実行とスケジューリング
Spring Framework は、それぞれ TaskExecutor
インターフェースと TaskScheduler
インターフェースを使用したタスクの非同期実行とスケジューリングの抽象化を提供します。Spring は、アプリケーションサーバー環境内でスレッドプールや CommonJ への委譲をサポートするインターフェースの実装も備えています。最終的に、共通インターフェースの背後でこれらの実装を使用すると、Java SE 環境と Jakarta EE 環境の違いが抽象化されます。
Spring は、Quartz スケジューラー (英語) とのスケジューリングをサポートする統合クラスも備えています。
Spring TaskExecutor
の抽象化
エグゼキュータは、スレッドプールの概念の JDK 名です。"executor" の命名は、基礎となる実装が実際にプールであるという保証がないという事実によるものです。エグゼキュータはシングルスレッドでも同期でもかまいません。Spring の抽象化により、Java SE 環境と Jakarta EE 環境の間で実装の詳細が隠されます。
Spring の TaskExecutor
インターフェースは、java.util.concurrent.Executor
インターフェースと同一です。実際、元々、存在する主な理由は、スレッドプールを使用するときに Java 5 の必要性を抽象化することでした。インターフェースには、スレッドプールのセマンティクスと設定に基づいて実行するタスクを受け入れる単一のメソッド(execute(Runnable task)
)があります。
TaskExecutor
はもともと、他の Spring コンポーネントに、必要に応じてスレッドプーリングの抽象化を提供するために作成されました。ApplicationEventMulticaster
、JMS の AbstractMessageListenerContainer
、Quartz 統合などのコンポーネントはすべて、TaskExecutor
抽象化を使用してスレッドをプールします。ただし、Bean がスレッドプーリング動作を必要とする場合、この抽象化を独自のニーズに使用することもできます。
TaskExecutor
型
Spring には、TaskExecutor
の事前構築済み実装が多数含まれています。おそらく、自分で実装する必要はないはずです。Spring が提供するバリアントは次のとおりです。
SyncTaskExecutor
: この実装では、呼び出しを非同期で実行しません。代わりに、各呼び出しは呼び出しスレッドで行われます。これは主に、単純なテストケースなど、マルチスレッドが不要な状況で使用されます。SimpleAsyncTaskExecutor
: この実装ではスレッドは再利用されません。代わりに、呼び出しごとに新しいスレッドが開始されます。ただし、スロットが解放されるまで制限を超える呼び出しをブロックする同時実行制限はサポートされています。真のプーリングが必要な場合は、このリストの後半にあるThreadPoolTaskExecutor
を参照してください。これは、"virtualThreads" オプションが有効になっているときに JDK 21 の仮想スレッドを使用します。この実装では、Spring のライフサイクル管理による正常なシャットダウンもサポートされています。ConcurrentTaskExecutor
: この実装は、java.util.concurrent.Executor
インスタンス用のアダプターです。Executor
構成パラメーターを Bean プロパティとして公開する代替手段(ThreadPoolTaskExecutor
)があります。ConcurrentTaskExecutor
を直接使用する必要はほとんどありません。ただし、ThreadPoolTaskExecutor
がニーズに対して十分な柔軟性がない場合は、ConcurrentTaskExecutor
が代替です。ThreadPoolTaskExecutor
: この実装は最も一般的に使用されています。java.util.concurrent.ThreadPoolExecutor
を構成するための Bean プロパティを公開し、それをTaskExecutor
にラップします。別の種類のjava.util.concurrent.Executor
に適応する必要がある場合は、代わりにConcurrentTaskExecutor
を使用することをお勧めします。また、Spring のライフサイクル管理を通じて、一時停止 / 再開機能と正常なシャットダウンも提供します。DefaultManagedTaskExecutor
: この実装では、JSR-236 互換のランタイム環境(Jakarta EE アプリケーションサーバーなど)で JNDI が取得したManagedExecutorService
を使用し、そのために CommonJ WorkManager を置き換えます。
TaskExecutor
を使用する
Spring の TaskExecutor
実装は、依存関係の注入でよく使用されます。次の例では、ThreadPoolTaskExecutor
を使用して一連のメッセージを非同期に出力する Bean を定義します。
Java
Kotlin
public class TaskExecutorExample {
private class MessagePrinterTask implements Runnable {
private String message;
public MessagePrinterTask(String message) {
this.message = message;
}
public void run() {
System.out.println(message);
}
}
private TaskExecutor taskExecutor;
public TaskExecutorExample(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
public void printMessages() {
for(int i = 0; i < 25; i++) {
taskExecutor.execute(new MessagePrinterTask("Message" + i));
}
}
}
class TaskExecutorExample(private val taskExecutor: TaskExecutor) {
private inner class MessagePrinterTask(private val message: String) : Runnable {
override fun run() {
println(message)
}
}
fun printMessages() {
for (i in 0..24) {
taskExecutor.execute(
MessagePrinterTask(
"Message$i"
)
)
}
}
}
ご覧のように、プールからスレッドを取得して自分で実行するのではなく、Runnable
をキューに追加します。次に、TaskExecutor
はその内部ルールを使用して、タスクが実行されるタイミングを決定します。
TaskExecutor
が使用するルールを構成するには、簡単な Bean プロパティを公開します。
Java
Kotlin
XML
@Bean
ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(10);
taskExecutor.setQueueCapacity(25);
return taskExecutor;
}
@Bean
TaskExecutorExample taskExecutorExample(ThreadPoolTaskExecutor taskExecutor) {
return new TaskExecutorExample(taskExecutor);
}
@Bean
fun taskExecutor() = ThreadPoolTaskExecutor().apply {
corePoolSize = 5
maxPoolSize = 10
queueCapacity = 25
}
@Bean
fun taskExecutorExample(taskExecutor: ThreadPoolTaskExecutor) = TaskExecutorExample(taskExecutor)
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5"/>
<property name="maxPoolSize" value="10"/>
<property name="queueCapacity" value="25"/>
</bean>
<bean id="taskExecutorExample" class="TaskExecutorExample">
<constructor-arg ref="taskExecutor"/>
</bean>
ほとんどの TaskExecutor
実装では、TaskDecorator
で送信されたタスクを自動的にラップする方法が提供されています。デコレータは、ラップするタスクに委譲し、タスクの実行前または実行後にカスタム動作を実装する必要があります。
タスクの実行前と実行後にメッセージを記録する簡単な実装を考えてみましょう。
Java
Kotlin
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.task.TaskDecorator;
public class LoggingTaskDecorator implements TaskDecorator {
private static final Log logger = LogFactory.getLog(LoggingTaskDecorator.class);
@Override
public Runnable decorate(Runnable runnable) {
return () -> {
logger.debug("Before execution of " + runnable);
runnable.run();
logger.debug("After execution of " + runnable);
};
}
}
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.springframework.core.task.TaskDecorator
class LoggingTaskDecorator : TaskDecorator {
override fun decorate(runnable: Runnable): Runnable {
return Runnable {
logger.debug("Before execution of $runnable")
runnable.run()
logger.debug("After execution of $runnable")
}
}
companion object {
private val logger: Log = LogFactory.getLog(
LoggingTaskDecorator::class.java
)
}
}
次に、TaskExecutor
インスタンスでデコレータを設定します。
Java
Kotlin
XML
@Bean
ThreadPoolTaskExecutor decoratedTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setTaskDecorator(new LoggingTaskDecorator());
return taskExecutor;
}
@Bean
fun decoratedTaskExecutor() = ThreadPoolTaskExecutor().apply {
setTaskDecorator(LoggingTaskDecorator())
}
<bean id="decoratedTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="taskDecorator" ref="loggingTaskDecorator"/>
</bean>
複数のデコレータが必要な場合は、org.springframework.core.task.support.CompositeTaskDecorator
を使用して複数のデコレータを順番に実行できます。
Spring TaskScheduler
の抽象化
TaskExecutor
抽象化に加えて、Spring には TaskScheduler
SPI があり、将来のある時点で実行するタスクをスケジュールするためのさまざまな方法があります。次のリストは、TaskScheduler
インターフェース定義を示しています。
public interface TaskScheduler {
Clock getClock();
ScheduledFuture schedule(Runnable task, Trigger trigger);
ScheduledFuture schedule(Runnable task, Instant startTime);
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
最も単純な方法は、Runnable
と Instant
のみを取る schedule
という名前の方法です。これにより、タスクは指定された時間の後に 1 回実行されます。他のすべての方法では、繰り返し実行するタスクをスケジュールできます。固定レートと固定遅延の方法は単純で定期的な実行用ですが、Trigger
を受け入れる方法ははるかに柔軟です。
Trigger
インターフェース
Trigger
インターフェースは、本質的に JSR-236 に触発されています。Trigger
の基本的な考え方は、過去の実行結果や任意の条件に基づいて実行時間を決定できるということです。これらの決定で前の実行の結果が考慮される場合、その情報は TriggerContext
内で使用できます。次のリストが示すように、Trigger
インターフェース自体は非常に単純です。
public interface Trigger {
Instant nextExecution(TriggerContext triggerContext);
}
TriggerContext
は最も重要な部分です。すべての関連データをカプセル化し、必要に応じて将来の拡張に備えています。TriggerContext
はインターフェースです(デフォルトでは SimpleTriggerContext
実装が使用されます)。次のリストは、Trigger
実装で使用可能なメソッドを示しています。
public interface TriggerContext {
Clock getClock();
Instant lastScheduledExecution();
Instant lastActualExecution();
Instant lastCompletion();
}
Trigger
の実装
Spring は、Trigger
インターフェースの 2 つの実装を提供します。最も興味深いのは CronTrigger
です。これにより、cron 式に基づいてタスクのスケジューリングが可能になります。例: 次のタスクは毎時 15 分後に実行されるようにスケジュールされていますが、平日の 9 時から 5 時までの「営業時間」にのみ実行されます。
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
もう 1 つの実装は、固定期間、オプションの初期遅延値、期間を固定レートまたは固定遅延として解釈する必要があるかどうかを示すブール値を受け入れる PeriodicTrigger
です。TaskScheduler
インターフェースは、固定レートまたは固定遅延でタスクをスケジュールするためのメソッドをすでに定義しているため、これらのメソッドは可能な限り直接使用する必要があります。PeriodicTrigger
実装の価値は、Trigger
抽象化に依存するコンポーネント内で使用できることです。例: 定期的なトリガー、cron ベースのトリガー、さらにはカスタムトリガーの実装を交換可能に使用できると便利な場合があります。このようなコンポーネントは依存性注入を利用できるため、そのような Triggers
を外部で構成できるため、簡単に変更または拡張できます。
TaskScheduler
の実装
Spring の TaskExecutor
抽象化と同様、TaskScheduler
構成の主な利点は、アプリケーションのスケジューリングのニーズが デプロイ環境から切り離されることです。この抽象化レベルは、アプリケーション自体によってスレッドが直接作成されるべきではないアプリケーションサーバー環境にデプロイする場合に特に関連します。このようなシナリオのために、Spring は、Jakarta EE 環境で JSR-236 ManagedScheduledExecutorService
に委譲する DefaultManagedTaskScheduler
を提供します。
外部スレッド管理が要件でない場合は常に、より簡単な代替手段はアプリケーション内のローカル ScheduledExecutorService
セットアップです。これは Spring の ConcurrentTaskScheduler
を介して調整できます。便宜上、Spring は ThreadPoolTaskScheduler
も提供します。ThreadPoolTaskScheduler
は、ScheduledExecutorService
に内部的に委譲して、ThreadPoolTaskExecutor
のラインに沿って共通の Bean スタイルの構成を提供します。これらのバリアントは、寛容なアプリケーションサーバー環境、特に Tomcat および Jetty でのローカルに埋め込まれたスレッドプールのセットアップでも完全に機能します。
6.1 以降、ThreadPoolTaskScheduler
は、Spring のライフサイクル管理を通じて一時停止 / 再開機能と正常なシャットダウンを提供します。また、JDK 21 の仮想スレッドに合わせた SimpleAsyncTaskScheduler
と呼ばれる新しいオプションもあり、単一のスケジューラースレッドを使用しますが、スケジュールされたタスクの実行ごとに新しいスレッドを起動します (すべて単一のスケジューラースレッドで動作する固定遅延タスクを除く)。この仮想スレッドに合わせたオプションでは、固定レートと cron トリガーが推奨されます)。
スケジューリングと非同期実行のアノテーションサポート
Spring は、タスクスケジューリングと非同期メソッド実行の両方にアノテーションサポートを提供します。
スケジューリングアノテーションを有効にする
@Scheduled
および @Async
アノテーションのサポートを有効にするには、次の例に示すように、@Configuration
クラスまたは <task:annotation-driven>
要素の 1 つに @EnableScheduling
および @EnableAsync
を追加します。
Java
Kotlin
XML
@Configuration
@EnableAsync
@EnableScheduling
public class SchedulingConfiguration {
}
@Configuration
@EnableAsync
@EnableScheduling
class SchedulingConfiguration
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/task
https://www.springframework.org/schema/task/spring-task.xsd">
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>
</beans>
アプリケーションに関連するアノテーションを選択できます。例: @Scheduled
のサポートのみが必要な場合は、@EnableAsync
を省略できます。よりきめ細かな制御を行うには、SchedulingConfigurer
インターフェース、AsyncConfigurer
インターフェース、その両方を追加で実装できます。詳細については、SchedulingConfigurer
(Javadoc) および AsyncConfigurer
javadoc を参照してください。
上記の XML では、@Async
アノテーションを持つメソッドに対応するタスクを処理するためのエグゼキューターリファレンスが提供され、@Scheduled
アノテーションが付けられたメソッドを管理するためのスケジューラリファレンスが提供されることに注意してください。
@Async アノテーションを処理するためのデフォルトのアドバイスモードは proxy であり、プロキシを介した呼び出しのみのインターセプトを許可します。同じクラス内のローカル呼び出しは、そのようにインターセプトすることはできません。より高度なインターセプトモードについては、コンパイル時またはロード時のウィービングと組み合わせて aspectj モードに切り替えることを検討してください。 |
@Scheduled
アノテーション
トリガーメタデータとともに、@Scheduled
アノテーションをメソッドに追加できます。例: 次のメソッドは、固定遅延で 5 秒(5000 ミリ秒)ごとに呼び出されます。つまり、期間は、先行する各呼び出しの完了時間から測定されます。
@Scheduled(fixedDelay = 5000)
public void doSomething() {
// something that should run periodically
}
デフォルトでは、ミリ秒は、固定遅延、固定レート、初期遅延値の時間単位として使用されます。秒や分などの別の時間単位を使用する場合は、 例: 前の例は次のように書くこともできます。
|
固定レートの実行が必要な場合は、アノテーション内で fixedRate
属性を使用できます。次のメソッドは 5 秒ごとに呼び出されます (各呼び出しの連続した開始時間の間で測定)。
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() {
// something that should run periodically
}
固定遅延タスクと固定レートタスクの場合、次の fixedRate
の例に示すように、メソッドの最初の実行前に待機する時間を指定することで、初期遅延を指定できます。
@Scheduled(initialDelay = 1000, fixedRate = 5000)
public void doSomething() {
// something that should run periodically
}
1 回限りのタスクの場合は、メソッドの目的の実行前に待機する時間を示すことで、初期遅延を指定するだけです。
@Scheduled(initialDelay = 1000)
public void doSomething() {
// something that should run only once
}
単純な定期的なスケジューリングでは表現力が不十分な場合は、cron 式を指定できます。次の例は平日のみ実行されます。
@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should run on weekdays only
}
zone 属性を使用して、cron 式が解決されるタイムゾーンを指定することもできます。 |
スケジュールするメソッドには void リターンが必要であり、引数を受け入れてはならないことに注意してください。メソッドがアプリケーションコンテキストから他のオブジェクトと対話する必要がある場合、通常、依存性注入によって提供されます。
@Scheduled
は反復可能なアノテーションとして使用できます。同じメソッドで複数のスケジュールされた宣言が見つかった場合、それぞれ独立して処理され、それぞれに対して個別のトリガーが起動されます。結果として、そのような同じ場所に配置されたスケジュールは重複し、並行してまたはすぐに連続して複数回実行される可能性があります。指定した cron 式などが誤って重複しないように注意してください。
Spring Framework 4.3 以降、 実行時に同じ |
Reactive メソッドまたは Kotlin サスペンド関数の @Scheduled
アノテーション
Spring Framework 6.1 以降、@Scheduled
メソッドはいくつかの型のリアクティブメソッドでもサポートされています。
次の例のように、戻り値の型が
Publisher
のメソッド (またはPublisher
の具体的な実装)。
@Scheduled(fixedDelay = 500)
public Publisher<Void> reactiveSomething() {
// return an instance of Publisher
}
次の例のように、型が遅延サブスクリプションをサポートしている場合に限り、
ReactiveAdapterRegistry
の共有インスタンスを介してPublisher
に適合できる戻り型を持つメソッド。
@Scheduled(fixedDelay = 500)
public Single<String> rxjavaNonPublisher() {
return Single.just("example");
}
|
次の例のような、Kotlin 一時停止機能:
@Scheduled(fixedDelay = 500)
suspend fun something() {
// do something asynchronous
}
次の例のように、Kotlin
Flow
またはDeferred
インスタンスを返すメソッド。
@Scheduled(fixedDelay = 500)
fun something(): Flow<Void> {
flow {
// do something asynchronous
}
}
これらの型のメソッドはすべて、引数なしで宣言する必要があります。Kotlin サスペンド関数の場合、フレームワークがサスペンド関数を Publisher
として呼び出すことができるように、kotlinx.coroutines.reactor
ブリッジも存在する必要があります。
Spring Framework は、アノテーション付きメソッドの Publisher
を一度取得し、その Publisher
をサブスクライブする Runnable
をスケジュールします。これらの内部定期購読は、対応する cron
/fixedDelay
/fixedRate
構成に従って発生します。
Publisher
が onNext
シグナルを発行する場合、これらは無視され、破棄されます (同期 @Scheduled
メソッドからの戻り値が無視されるのと同じ方法です)。
次の例では、Flux
は onNext("Hello")
、onNext("World")
を 5 秒ごとに発行しますが、これらの値は使用されません。
@Scheduled(initialDelay = 5000, fixedRate = 5000)
public Flux<String> reactiveSomething() {
return Flux.just("Hello", "World");
}
Publisher
が onError
シグナルを発信すると、WARN
レベルで記録され、回復されます。Publisher
インスタンスの非同期で遅延的な性質のため、例外は Runnable
タスクからスローされません。これは、ErrorHandler
契約がリアクティブメソッドに関与しないことを意味します。
その結果、エラーにもかかわらず、さらにスケジュールされたサブスクリプションが発生します。
次の例では、Mono
サブスクリプションが最初の 5 秒間に 2 回失敗します。その後、サブスクリプションが成功し始め、5 秒ごとにメッセージが標準出力に出力されます。
@Scheduled(initialDelay = 0, fixedRate = 5000)
public Mono<Void> reactiveSomething() {
AtomicInteger countdown = new AtomicInteger(2);
return Mono.defer(() -> {
if (countDown.get() == 0 || countDown.decrementAndGet() == 0) {
return Mono.fromRunnable(() -> System.out.println("Message"));
}
return Mono.error(new IllegalStateException("Cannot deliver message"));
})
}
アノテーション付き Bean を破棄するか、アプリケーションコンテキストを閉じると、Spring Framework はスケジュールされたタスクをキャンセルします。これには、 |
@Async
アノテーション
メソッドに @Async
アノテーションを付けて、そのメソッドの呼び出しが非同期に行われるようにすることができます。つまり、呼び出し元は呼び出し時にすぐに戻りますが、メソッドの実際の実行は Spring TaskExecutor
に送信されたタスクで発生します。最も単純なケースでは、次の例に示すように、void
を返すメソッドにアノテーションを適用できます。
@Async
void doSomething() {
// this will be run asynchronously
}
@Scheduled
アノテーションが付けられたメソッドとは異なり、これらのメソッドは、コンテナーによって管理されているスケジュールされたタスクからではなく、実行時に呼び出し元によって「通常」の方法で呼び出されるため、引数を期待できます。例: 次のコードは、@Async
アノテーションの正当なアプリケーションです。
@Async
void doSomething(String s) {
// this will be run asynchronously
}
値を返すメソッドでさえ非同期に呼び出すことができます。ただし、そのようなメソッドには Future
-typed 戻り値が必要です。これにより、非同期実行の利点が得られるため、呼び出し元は、Future
で get()
を呼び出す前に他のタスクを実行できます。次の例は、値を返すメソッドで @Async
を使用する方法を示しています。
@Async
Future<String> returnSomething(int i) {
// this will be run asynchronously
}
@Async メソッドは、通常の java.util.concurrent.Future 戻り値型を宣言するだけでなく、Spring の org.springframework.util.concurrent.ListenableFuture を宣言することもできます。Spring 4.2 では、JDK 8 の java.util.concurrent.CompletableFuture を使用して、非同期タスクとのやり取りを増やしたり、追加の処理ステップを使用して即座に構成したりできます。 |
@Async
を @PostConstruct
などのライフサイクルコールバックと組み合わせて使用することはできません。Spring Bean を非同期的に初期化するには、現在、次の例に示すように、個別の初期化 Spring Bean を使用して、ターゲットで @Async
アノテーション付きメソッドを呼び出す必要があります。
public class SampleBeanImpl implements SampleBean {
@Async
void doSomething() {
// ...
}
}
public class SampleBeanInitializer {
private final SampleBean bean;
public SampleBeanInitializer(SampleBean bean) {
this.bean = bean;
}
@PostConstruct
public void initialize() {
bean.doSomething();
}
}
@Async に相当する直接的な XML はありません。そのようなメソッドは、非同期的に実行するために外部で再宣言するのではなく、最初は非同期実行用に設計する必要があるためです。ただし、Spring AOP を使用して Spring の AsyncExecutionInterceptor をカスタムポイントカットと組み合わせて手動でセットアップできます。 |
@Async
によるエグゼキューター修飾
デフォルトでは、メソッドで @Async
を指定する場合、使用されるエグゼキューターは、非同期サポートを有効にするときに構成されたもの、つまり、XML を使用している場合は “annotation-driven” 要素、または存在する場合は AsyncConfigurer
実装を使用している場合です。ただし、特定のメソッドを実行するときにデフォルト以外のエグゼキューターを使用する必要があることを示す必要がある場合は、@Async
アノテーションの value
属性を使用できます。次の例は、その方法を示しています。
@Async("otherExecutor")
void doSomething(String s) {
// this will be run asynchronously by "otherExecutor"
}
この場合、"otherExecutor"
は、Spring コンテナー内の Executor
Bean の名前にすることも、Executor
に関連付けられた修飾子の名前にすることもできます(たとえば、<qualifier>
エレメントまたは Spring の @Qualifier
アノテーションで指定)。
@Async
を使用した例外管理
@Async
メソッドに Future
-typed 戻り値がある場合、メソッド実行中にスローされた例外を管理するのは簡単です。この例外は、Future
の結果で get
を呼び出すときにスローされるためです。ただし、void
戻り値の型では、例外はキャッチされず、送信できません。このような例外を処理する AsyncUncaughtExceptionHandler
を提供できます。次の例は、その方法を示しています。
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// handle exception
}
}
デフォルトでは、例外は単にログに記録されます。AsyncConfigurer
または <task:annotation-driven/>
XML エレメントを使用して、カスタム AsyncUncaughtExceptionHandler
を定義できます。
task
名前空間
バージョン 3.0 の時点で、Spring には TaskExecutor
および TaskScheduler
インスタンスを構成するための XML 名前空間が含まれています。また、トリガーを使用してスケジュールされるタスクを構成する便利な方法も提供します。
scheduler
要素
次の要素は、指定されたスレッドプールサイズで ThreadPoolTaskScheduler
インスタンスを作成します。
<task:scheduler id="scheduler" pool-size="10"/>
id
属性に指定された値は、プール内のスレッド名のプレフィックスとして使用されます。scheduler
要素は比較的簡単です。pool-size
属性を指定しない場合、デフォルトのスレッドプールには単一のスレッドしかありません。スケジューラの他の構成オプションはありません。
executor
要素
以下は、ThreadPoolTaskExecutor
インスタンスを作成します。
<task:executor id="executor" pool-size="10"/>
前のセクションで示したスケジューラーと同様に、id
属性に指定された値は、プール内のスレッド名のプレフィックスとして使用されます。プールサイズに関する限り、executor
要素は scheduler
要素よりも多くの構成オプションをサポートしています。ひとつには、ThreadPoolTaskExecutor
のスレッドプール自体がより構成可能です。エグゼキュータのスレッドプールは、単一のサイズではなく、コアと最大サイズに異なる値を設定できます。単一の値を指定すると、executor には固定サイズのスレッドプールがあります(コアサイズと最大サイズは同じです)。ただし、executor
要素の pool-size
属性は、min-max
の形式の範囲も受け入れます。次の例では、5
の最小値と 25
の最大値を設定します。
<task:executor
id="executorWithPoolSizeRange"
pool-size="5-25"
queue-capacity="100"/>
上記の構成では、queue-capacity
値も提供されています。スレッドプールの構成も、エグゼキューターのキュー容量を考慮して考慮する必要があります。プールサイズとキュー容量の関連の詳細については、ThreadPoolExecutor
(標準 Javadoc) のドキュメントを参照してください。主なアイデアは、タスクが送信されると、アクティブなスレッドの数が現在コアサイズより少ない場合、エグゼキューターは最初に空きスレッドを使用しようとすることです。コアサイズに達した場合、その容量にまだ達していない限り、タスクはキューに追加されます。そのときだけ、キューの容量に達した場合、エグゼキューターはコアサイズを超える新しいスレッドを作成します。最大サイズにも達している場合、エグゼキューターはタスクを拒否します。
デフォルトでは、キューは無制限ですが、プールスレッドがすべてビジーである間に十分なタスクがキューに追加されると OutOfMemoryError
につながる可能性があるため、これはめったに望ましい構成ではありません。さらに、キューが制限されていない場合、最大サイズはまったく効果がありません。executor は常にコアサイズを超えて新しいスレッドを作成する前にキューを試行するため、スレッドプールがコアサイズを超えて大きくなるには、キューに有限の容量が必要です(これは、使用時に固定サイズのプールのみが実用的な場合です無制限のキュー)。
上記のように、タスクが拒否された場合を考えてください。デフォルトでは、タスクが拒否されると、スレッドプールエグゼキューターは TaskRejectedException
をスローします。ただし、拒否ポリシーは実際に構成可能です。AbortPolicy
実装であるデフォルトの拒否ポリシーを使用すると、例外がスローされます。重い負荷でいくつかのタスクをスキップできるアプリケーションでは、代わりに DiscardPolicy
または DiscardOldestPolicy
を構成できます。重い負荷で送信されたタスクを調整する必要があるアプリケーションに適した別のオプションは、CallerRunsPolicy
です。このポリシーは、例外をスローしたりタスクを破棄したりする代わりに、submit メソッドを呼び出しているスレッドにタスク自体を実行させます。そのような発呼者は、そのタスクの実行中はビジーであり、他のタスクをすぐに送信できないという考えです。スレッドプールとキューの制限を維持しながら、受信負荷を調整する簡単な方法を提供します。通常、これにより、executor は処理中のタスクを「追いつく」ことができ、キュー、プール、その両方の容量を解放できます。executor
要素の rejection-policy
属性で使用可能な値の列挙から、これらのオプションのいずれかを選択できます。
次の例は、さまざまな動作を指定する多数の属性を持つ executor
要素を示しています。
<task:executor
id="executorWithCallerRunsPolicy"
pool-size="5-25"
queue-capacity="100"
rejection-policy="CALLER_RUNS"/>
最後に、keep-alive
設定は、スレッドが停止する前にアイドル状態を維持できる時間制限(秒単位)を決定します。現在プールにあるスレッドのコア数を超える場合、タスクを処理せずにこの時間待機した後、余分なスレッドは停止します。時間値がゼロの場合、タスクキューにフォローアップ作業が残っていないまま、タスクの実行直後に余分なスレッドが停止します。次の例では、keep-alive
値を 2 分に設定します。
<task:executor
id="executorWithKeepAlive"
pool-size="5-25"
keep-alive="120"/>
scheduled-tasks
要素
Spring のタスク名前空間の最も強力な機能は、Spring アプリケーションコンテキスト内でスケジュールされるようにタスクを構成するためのサポートです。これは、メッセージ駆動型 POJO を構成するために JMS 名前空間によって提供されるものなど、Spring の他の「メソッド呼び出し側」と同様のアプローチに従います。基本的に、ref
属性は任意の Spring 管理対象オブジェクトを指すことができ、method
属性はそのオブジェクトで呼び出されるメソッドの名前を提供します。次のリストは、簡単な例を示しています。
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>
</task:scheduled-tasks>
<task:scheduler id="myScheduler" pool-size="10"/>
スケジューラーは外部要素によって参照され、個々のタスクにはトリガーメタデータの構成が含まれます。前の例では、そのメタデータは、各タスクの実行が完了した後に待機するミリ秒数を示す固定遅延を伴う定期的なトリガーを定義します。もう 1 つのオプションは fixed-rate
で、以前の実行にかかる時間に関係なく、メソッドを実行する頻度を示します。さらに、fixed-delay
タスクと fixed-rate
タスクの両方で、メソッドの最初の実行まで待機するミリ秒数を示す "initial-delay" パラメーターを指定できます。より詳細に制御するために、代わりに cron
属性を指定して cron 式を提供できます。次の例は、これらの他のオプションを示しています。
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>
<task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/>
<task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>
</task:scheduled-tasks>
<task:scheduler id="myScheduler" pool-size="10"/>
CRON 式
すべての Spring cron 式は、@Scheduled
アノテーション、task:scheduled-tasks
要素、その他の場所で使用しているかどうかに関係なく、同じ形式に準拠する必要があります。* * * * * *
などの整形式の cron 式は、スペースで区切られた 6 つの時刻フィールドと日付フィールドで構成され、それぞれに有効な値の範囲があります。
┌───────────── second (0-59) │ ┌───────────── minute (0 - 59) │ │ ┌───────────── hour (0 - 23) │ │ │ ┌───────────── day of the month (1 - 31) │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC) │ │ │ │ │ ┌───────────── day of the week (0 - 7) │ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN) │ │ │ │ │ │ * * * * * *
適用されるいくつかのルールがあります。
フィールドはアスタリスク(
*
)である場合があり、これは常に「最初から最後」を表します。曜日または曜日のフィールドには、アスタリスクの代わりに疑問符(?
)を使用できます。カンマ(
,
)は、リストの項目を区切るために使用されます。ハイフン(
-
)で区切られた 2 つの数値は、数値の範囲を表します。指定された範囲は包括的です。/
を使用して範囲(または*
)を追跡すると、範囲内の数値の値の間隔が指定されます。月と曜日のフィールドには、英語の名前も使用できます。特定の日または月の最初の 3 文字を使用します (大文字と小文字は区別されません)。
月日および曜日フィールドには、異なる意味を持つ
L
文字を含めることができます。日フィールドでは、
L
は月の最後の日を表します。負のオフセット(つまり、L-n
)が後に続く場合、n
の月の最後の日を意味します。曜日フィールドでは、
L
は週の最後の日を表します。接頭辞として数字または 3 文字の名前(dL
またはDDDL
)が付いている場合は、その月の最後の曜日(d
またはDDD
)を意味します。
日フィールドは
nW
にすることができます。これは、月のn
に最も近い曜日を表します。n
が土曜日に当たる場合、これはその前の金曜日になります。n
が日曜日に該当する場合、これは翌月曜日になります。これは、n
が1
であり、土曜日に該当する場合にも発生します(つまり、1W
は月の最初の平日を表します)。日フィールドが
LW
の場合、その月の最後の平日を意味します。曜日フィールドは、
d#n
(またはDDD#n
)にすることができます。これは、月のn
番目の曜日d
(またはDDD
)を表します。
ここではいくつかの例を示します。
cron 式 | 意味 |
---|---|
| 毎日 0 時 0 分 |
| 10 秒ごと |
| 毎日 8, 9, 10 時 |
| 毎日 6:00 AM、7:00 PM |
| 毎日 8:00, 8:30, 9:00, 9:30, 10:00, 10:30 |
| 平日の 9 時から 5 時 |
| 毎年クリスマスの真夜中 |
| 月の最終日深夜 |
| 月の最後から 3 番目の深夜 |
| 月の最終金曜日の深夜 |
| 月の最終木曜日の深夜 |
| 月の最初の平日深夜 |
| 月の最後の平日深夜 |
| 月の第 2 金曜日の深夜 |
| 月の最初の月曜日の深夜 |
マクロ
0 0 * * * *
などの式は、人間が解析するのが難しいため、バグが発生した場合に修正するのが困難です。読みやすさを向上させるために、Spring は、一般的に使用されるシーケンスを表す次のマクロをサポートしています。6 桁の値の代わりにこれらのマクロを使用できます。つまり、@Scheduled(cron = "@hourly")
です。
マクロ | 意味 |
---|---|
|
once a year ( |
| 1 ヶ月に 1 回 ( |
| 1 週間に 1 回 ( |
|
once a day ( |
| 1 時間に 1 回 ( |
Quartz スケジューラーの使用
Quartz は Trigger
、Job
、JobDetail
オブジェクトを使用して、あらゆる種類のジョブのスケジューリングを実現します。Quartz の背後にある基本概念については、Quartz Web サイト (英語) を参照してください。便宜上、Spring は、Spring ベースのアプリケーション内で Quartz の使用を簡素化するいくつかのクラスを提供します。
JobDetailFactoryBean
を使用する
Quartz JobDetail
オブジェクトには、ジョブの実行に必要なすべての情報が含まれています。Spring は JobDetailFactoryBean
を提供し、XML 構成のために Bean スタイルのプロパティを提供します。次の例を考えてみましょう。
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="example.ExampleJob"/>
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="5"/>
</map>
</property>
</bean>
ジョブ詳細構成には、ジョブの実行に必要なすべての情報が含まれています(ExampleJob
)。タイムアウトは、ジョブデータマップで指定されます。ジョブデータマップは JobExecutionContext
を介して使用できます(実行時に渡されます)が、JobDetail
はジョブインスタンスのプロパティにマップされたジョブデータからそのプロパティも取得します。次の例では、ExampleJob
には timeout
という名前の Bean プロパティが含まれ、JobDetail
にはそれが自動的に適用されます。
package example;
public class ExampleJob extends QuartzJobBean {
private int timeout;
/**
* Setter called after the ExampleJob is instantiated
* with the value from the JobDetailFactoryBean.
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
// do the actual work
}
}
ジョブデータマップのすべての追加プロパティも利用できます。
name プロパティと group プロパティを使用して、ジョブの名前とグループをそれぞれ変更できます。デフォルトでは、ジョブの名前は JobDetailFactoryBean の Bean 名(上記の例では exampleJob )と一致します。 |
MethodInvokingJobDetailFactoryBean
を使用する
多くの場合、特定のオブジェクトのメソッドを呼び出すだけで済みます。MethodInvokingJobDetailFactoryBean
を使用すると、次の例に示すように、まさにこれを実行できます。
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject"/>
<property name="targetMethod" value="doIt"/>
</bean>
上記の例では、次の例に示すように、doIt
メソッドが exampleBusinessObject
メソッドで呼び出されます。
public class ExampleBusinessObject {
// properties and collaborators
public void doIt() {
// do the actual work
}
}
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
MethodInvokingJobDetailFactoryBean
を使用することにより、メソッドを呼び出すだけの 1 行のジョブを作成する必要がなくなります。実際のビジネスオブジェクトを作成し、詳細オブジェクトを結び付けるだけです。
デフォルトでは、Quartz ジョブはステートレスであるため、ジョブが互いに干渉する機能があります。同じ JobDetail
に 2 つのトリガーを指定すると、最初のジョブが完了する前に 2 番目のトリガーが開始される機能があります。JobDetail
クラスが Stateful
インターフェースを実装している場合、これは起こりません。最初のジョブが完了する前に 2 番目のジョブが開始されません。
MethodInvokingJobDetailFactoryBean
から生じるジョブを非並行にするには、次の例に示すように、concurrent
フラグを false
に設定します。
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject"/>
<property name="targetMethod" value="doIt"/>
<property name="concurrent" value="false"/>
</bean>
デフォルトでは、ジョブは並行して実行されます。 |
トリガーと SchedulerFactoryBean
を使用してジョブを結び付ける
ジョブの詳細とジョブを作成しました。また、特定のオブジェクトでメソッドを呼び出すことができる便利な Bean を確認しました。もちろん、ジョブ自体をスケジュールする必要があります。これは、トリガーと SchedulerFactoryBean
を使用して行われます。Quartz 内ではいくつかのトリガーを使用できます。Spring は、CronTriggerFactoryBean
と SimpleTriggerFactoryBean
の便利なデフォルトを備えた 2 つの Quartz FactoryBean
実装を提供します。
トリガーをスケジュールする必要があります。Spring は、プロパティとして設定されるトリガーを公開する SchedulerFactoryBean
を提供します。SchedulerFactoryBean
は、これらのトリガーを使用して実際のジョブをスケジュールします。
次のリストでは、SimpleTriggerFactoryBean
と CronTriggerFactoryBean
の両方を使用しています。
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<!-- see the example of method invoking job above -->
<property name="jobDetail" ref="jobDetail"/>
<!-- 10 seconds -->
<property name="startDelay" value="10000"/>
<!-- repeat every 50 seconds -->
<property name="repeatInterval" value="50000"/>
</bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="exampleJob"/>
<!-- run every morning at 6 AM -->
<property name="cronExpression" value="0 0 6 * * ?"/>
</bean>
上記の例では、2 つのトリガーをセットアップします。1 つは 50 秒ごとに実行され、開始遅延は 10 秒で、もう 1 つは毎朝午前 6 時に実行されます。すべてをファイナライズするには、次の例に示すように、SchedulerFactoryBean
をセットアップする必要があります。
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
<ref bean="simpleTrigger"/>
</list>
</property>
</bean>
ジョブの詳細で使用されるカレンダー、Quartz をカスタマイズするためのプロパティ、Spring が提供する JDBC DataSource など、SchedulerFactoryBean
で使用できるプロパティは他にもあります。詳細については、SchedulerFactoryBean
javadoc を参照してください。
SchedulerFactoryBean は、通常の Quartz 構成と同様に、Quartz プロパティキーに基づいて、クラスパス内の quartz.properties ファイルも認識します。多くの SchedulerFactoryBean 設定は、プロパティファイルの一般的な Quartz 設定と相互作用することに注意してください。両方のレベルで値を指定することはお勧めしません。例: Spring が提供する DataSource に依存する場合は、"org.quartz.jobStore.class" プロパティを設定しないでください。または、標準の org.quartz.impl.jdbcjobstore.JobStoreTX の本格的な代替である org.springframework.scheduling.quartz.LocalDataSourceJobStore バリアントを指定してください。 |