
CVE-2026-41710 向けのニッチなツールキットを備えた spring retry 1.3.x 修正
このプロジェクトは、Springアプリケーション向けの宣言的リトライサポートを提供します。Spring Batch、Spring Integrationなどで使用されています。 明示的な使用のための命令的リトライもサポートされています。
このバージョンは、java8およびspring4またはspring5向けにspring-retryを2.0.13で修正したものです。 詳細については、公式のSpring 2.0.13バージョンを参照してください。
このセクションでは、Spring Retryを使い始めるための簡単な紹介を提供します。 宣言的な例と命令的な例が含まれています。
次の例は、宣言的なスタイルでSpring Retryを使用する方法を示しています:
@Configuration
@EnableRetry
public class Application {
@Bean
public Service service() {
return new Service();
}
}
@Service
class Service {
@Retryable(RemoteAccessException.class)
public void service() {
// ... do something
}
@Recover
public void recover(RemoteAccessException e) {
// ... panic
}
}
この例は service メソッドを呼び出し、RemoteAccessException で失敗した場合は再試行し(デフォルトでは最大3回)、それでも成功しない場合は recover メソッドを試みます。
@Retryable アノテーションの属性には、例外型の包含と除外、再試行回数の制限、バックオフポリシーの設定など、さまざまなオプションがあります。
先に示した @Retryable アノテーションを使用して再試行処理を適用する宣言的なアプローチには、AOP クラスへの追加のランタイム依存関係があります。プロジェクトでこの依存関係を解決する方法の詳細については、
「再試行プロキシの Java 設定」 セクションを参照してください。
次の例は、Spring Retry を命令型スタイルで使用する方法を示しています(バージョン 1.3 以降で利用可能):
RetryTemplate template = RetryTemplate.builder()
.maxAttempts(3)
.fixedBackoff(1000)
.retryOn(RemoteAccessException.class)
.build();
template.execute(ctx -> {
// ... do something
});
1.3 より前のバージョンについては、RetryTemplate セクションの例を参照してください。
Spring Retry には Java 1.7 および Maven 3.0.5(以降)が必要です。 ビルドするには、次の Maven コマンドを実行します:
$ mvn install
このセクションでは、Spring Retry の機能について説明し、その API の使用方法を示します。
RetryTemplate の使用処理をより堅牢にし、失敗しにくくするために、失敗した操作が後続の試行で成功する可能性がある場合に、その操作を自動的に再試行することが役立つことがあります。この種の対処に適したエラーは、一時的な性質を持つものです。たとえば、ネットワークの不具合やデータベース更新時の DeadLockLoserException によって失敗した Web サービスや RMI サービスへのリモート呼び出しは、短時間の待機後に自然に解決する場合があります。このような操作の再試行を自動化するために、Spring Retry には RetryOperations 戦略があります。RetryOperations インターフェースの定義は次のとおりです。
public interface RetryOperations {
<T> T execute(RetryCallback<T> retryCallback) throws Exception;
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback)
throws Exception;
<T> T execute(RetryCallback<T> retryCallback, RetryState retryState)
throws Exception, ExhaustedRetryException;
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback,
RetryState retryState) throws Exception;
}
基本的なコールバックは、再試行するビジネスロジックを挿入できるシンプルなインターフェースです:
public interface RetryCallback<T> {
T doWithRetry(RetryContext context) throws Throwable;
}
コールバックが試行され、失敗した場合(Exception をスローすることにより)、成功するか実装が中止を決定するまで再試行されます。RetryOperations インターフェースには多数のオーバーロードされた execute メソッドがあり、すべての再試行が尽きた際のリカバリに関するさまざまなユースケースに対処し、またクライアントと実装が呼び出し間で情報を保存できる再試行状態(これについては後述)に対処します。
RetryOperations の最もシンプルな汎用実装は RetryTemplate です。次の例はその使用方法を示しています:
RetryTemplate template = new RetryTemplate();
TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
policy.setTimeout(30000L);
template.setRetryPolicy(policy);
Foo result = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// Do stuff that might fail, e.g. webservice operation
return result;
}
});
先ほどの例では、Web サービス呼び出しを実行し、その結果をユーザーに返します。 その呼び出しが失敗した場合、タイムアウトに達するまで再試行されます。
バージョン 1.3 以降では、次のように RetryTemplate の fluent 設定も利用できます:
RetryTemplate.builder()
.maxAttempts(10)
.exponentialBackoff(100, 2, 10000)
.retryOn(IOException.class)
.traversingCauses()
.build();
RetryTemplate.builder()
.fixedBackoff(10)
.withinMillis(3000)
.build();
RetryTemplate.builder()
.infiniteRetry()
.retryOn(IOException.class)
.uniformRandomBackoff(1000, 3000)
.build();
RetryContext の使用RetryCallback のメソッドパラメータは RetryContext です。多くのコールバックは
このコンテキストを無視します。ただし、必要に応じて、イテレーションの間データを
保存するための属性バッグとして使用できます。
RetryContext は、同じスレッド内でネストされたリトライが進行中の場合、親コンテキストを持ちます。
親コンテキストは、execute の呼び出し間で共有する必要があるデータを保存するのに
時折役立ちます。
RecoveryCallback の使用リトライが尽きると、RetryOperations は制御を別のコールバックである
RecoveryCallback に渡すことができます。この機能を使用するには、クライアントは
次の例に示すように、コールバックをまとめて同じメソッドに渡すことができます:
Foo foo = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// business logic here
},
new RecoveryCallback<Foo>() {
Foo recover(RetryContext context) throws Exception {
// recover logic here
}
});
ビジネスロジックがテンプレートの中止を決定する前に成功しなかった場合、クライアントにはリカバリーコールバックを通じて代替処理を行う機会が与えられます。
最も単純なケースでは、リトライは単なる while ループです。RetryTemplate は成功するか失敗するまで試行を続けることができます。RetryContext には、リトライするか中止するかを決定するための状態が含まれています。ただし、この状態はスタック上にあり、どこかにグローバルに保存する必要はありません。したがって、これを「ステートレスリトライ」と呼びます。ステートレスリトライとステートフルリトライの区別は、RetryPolicy の実装に含まれています(RetryTemplate は両方を扱えます)。ステートレスリトライでは、コールバックはリトライ時に失敗したときと同じスレッドで常に実行されます。
失敗によってトランザクションリソースが無効になった場合には、いくつかの特別な考慮事項があります。これは単純なリモート呼び出しには当てはまりません。なぜなら(通常)トランザクションリソースが存在しないからですが、データベース更新、特に Hibernate を使用する場合には当てはまることがあります。この場合、トランザクションをロールバックして新しい(かつ有効な)トランザクションを開始できるように、失敗を引き起こした例外を直ちに再スローすることだけが意味を持ちます。
このような場合、ステートレスリトライでは十分ではありません。なぜなら、再スローとロールバックは必然的に RetryOperations.execute() メソッドを抜け出し、スタック上にあったコンテキストを失う可能性があるからです。コンテキストを失わないためには、それをスタックから持ち上げて(最低限)ヒープストレージに置くためのストレージ戦略を導入する必要があります。この目的のために、Spring Retry は RetryContextCache と呼ばれるストレージ戦略を提供しており、これを RetryTemplate に注入できます。RetryContextCache のデフォルト実装は、シンプルな Map を使用したインメモリ実装です。メモリリークを避けるために厳格に適用される最大容量を持ちますが、高度なキャッシュ機能(time to live など)は持ちません。必要であれば、そうした機能を持つ Map を注入することを検討すべきです。クラスタ環境で複数プロセスを扱う高度な用途では、何らかのクラスタキャッシュで RetryContextCache を実装することも検討できます(ただし、クラスタ環境であってもこれは過剰かもしれません)。
RetryOperations の責任の一部は、失敗した操作が新しい実行で(通常は新しいトランザクションにラップされて)戻ってきたときにそれを認識することです。これを容易にするために、Spring Retry は RetryState 抽象化を提供します。これは RetryOperations の特別な execute メソッドと連携して動作します。
失敗した操作は、リトライの複数回の呼び出しにわたって状態を識別することによって認識されます。状態を識別するために、項目を識別する一意のキーを返す責任を持つ RetryState オブジェクトを提供できます。この識別子は RetryContextCache のキーとして使用されます。
警告:
RetryStateが返すキーにおけるObject.equals()とObject.hashCode()の実装には十分注意してください。最善のアドバイスは、項目を識別するためにビジネスキーを使用することです。JMS メッセージの場合は、メッセージ ID を使用できます。
リトライが尽きたときには、RetryCallback(今では失敗する可能性が高いと推定される)を呼び出す代わりに、失敗した項目を別の方法で処理するオプションもあります。ステートレスの場合と同様に、このオプションは RecoveryCallback によって提供され、RetryOperations の execute メソッドに渡すことで提供できます。
リトライするかどうかの決定は、実際には通常の RetryPolicy に委譲されるため、制限やタイムアウトに関する通常の懸念事項をそこに注入できます(追加の依存関係 セクションを参照)。
RetryTemplate 内では、execute メソッドで再試行するか失敗させるかの判断は RetryPolicy によって行われ、これは RetryContext のファクトリでもあります。RetryTemplate は、現在のポリシーを使用して RetryContext を作成し、それを毎回の試行時に RetryCallback に渡す責任を負います。コールバックが失敗した後、RetryTemplate は RetryPolicy を呼び出して状態(RetryContext に格納されている)を更新するよう要求する必要があります。その後、別の試行が可能かどうかをポリシーに問い合わせます。別の試行ができない場合(たとえば、制限に達したかタイムアウトが検出された場合)、ポリシーは枯渇状態を特定する責任も負いますが、例外の処理は行いません。RetryTemplate は元の例外をスローしますが、ステートフルな場合でリカバリが利用できないときは例外です。その場合、RetryExhaustedException をスローします。また、RetryTemplate にフラグを設定して、コールバック(つまりユーザーコード)から元の例外を無条件にスローさせることもできます。
ヒント: 失敗は本質的に再試行可能かそうでないかのどちらかです。同じ例外がビジネスロジックから常にスローされるのであれば、それを再試行しても役に立ちません。したがって、すべての例外タイプで再試行すべきではありません。むしろ、再試行可能と予想される例外のみに焦点を当てるようにしてください。より積極的に再試行することは通常ビジネスロジックに害を及ぼしませんが、失敗が決定的なものである場合、事前に致命的とわかっているものを再試行するのに時間を費やすことになるため、無駄です。
Spring Retry は、ステートレスな RetryPolicy のいくつかのシンプルな汎用実装(たとえば SimpleRetryPolicy)と、前の例で使用された TimeoutRetryPolicy を提供します。
SimpleRetryPolicy は、名前付きの例外タイプのリストのいずれかで、固定回数まで再試行を許可します。次の例はその使用方法を示しています: