RetryTemplateとは
RetryTemplateは、Spring Retryライブラリの中に含まれるオブジェクトで、例外発生時に処理を再試行する際の動作を柔軟に定義することができます。RetryTemplateを使うことで、繰り返し処理のロジックをカスタマイズすることが可能です。
主に一時的な失敗がしやすく、再試行することで成功するケースにおいて有効です。
- 外部API呼び出し
- DBのロックやトランザクション競合
実行環境
Java 21
SpringBoot 3.5.3
実装
Configクラス
/** リトライ可能回数 */
private static final Integer CAN_RETRY_COUNT = 5;
/** 待機時間 */
private static final Long WAIT_TIME = 2000L;
@Bean
public RetryTemplate retryTemplate() {
return RetryTemplate.builder()
.maxAttempts(CAN_RETRY_COUNT)
.fixedBackoff(WAIT_TIME)
.notRetryOn(HttpClientErrorException.class)
.withListener(
new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(RetryContext context,
RetryCallback<T, E> callback) {
log.info("+++処理前+++");
return true;
}
@Override
public <T, E extends Throwable> void close(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
log.info("+++処理後+++");
}
})
.build();
}
ロジッククラス
public void retry() {
retryTemplate.<Void, HttpServerErrorException>execute(context -> {
log.info("リトライ処理");
fakeCallExternalApi();
return null;
});
}
private void fakeCallExternalApi() {
throw new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR);
}
コード説明
- maxAttempts リトライ可能な回数
- fixedBackoff 次リトライ処理までの待機時間
- notRetryOn リトライをさせたくない例外。リトライを指定するretryOnとは排他的な関係なので、どちらかのみを指定する
- withListener RetryTemplateの追加したいリスナー
- executeメソッドの<T, E extends Throwable>のTには戻り値の型、Eには発生しうる例外の型
実行結果
+++処理前+++
リトライ処理
リトライ処理
リトライ処理
リトライ処理
リトライ処理
+++処理後+++
Exception in thread "main" org.springframework.web.client.HttpServerErrorException: 500 INTERNAL_SERVER_ERROR
at com.example.service.DemoRetryServiceImpl.fakeCallExternalApi(DemoRetryServiceImpl.java:28)
at com.example.service.DemoRetryServiceImpl.lambda$0(DemoRetryServiceImpl.java:21)
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:357)
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:230)
at com.example.service.DemoRetryServiceImpl.retry(DemoRetryServiceImpl.java:19)
at com.example.DemoRetrytemplateApplication.main(DemoRetrytemplateApplication.java:20)
リトライ回数の上限に達しても処理が成功しなかった場合は、最後の試行で発生した例外がスローされます。
for文で実装するのではダメなの?
簡単なリトライ処理であれば、for文を使って実装する方が良いです。しかし、リトライ前後のログ出力やある例外発生時にはリトライをさせない等の仕様があれば、RetryTemplateを用いる方がすっきりします。
RetryTemplateを使わず、for文を使って同じ処理を実装してみます。
public void retry() {
int maxAttempts = 5;
log.info("+++処理前+++");
for (int i = 0; i < maxAttempts; i++) {
try {
log.info("リトライ処理");
fakeCallExternalApi();
} catch (HttpClientErrorException e) {
break;
} catch (Exception e) {
if (i == maxAttempts - 1) {
log.info("+++処理後+++");
throw e;
}
sleep();
}
}
}
private void fakeCallExternalApi() {
throw new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR);
}
private void sleep() {
try {
// 待機
Thread.sleep(2000L);
} catch (InterruptedException e2) {
Thread.currentThread().interrupt();
}
}
ごちゃごちゃしていて見辛さがありますね。。。
終わりに
リトライ処理をカスタマイズできて、コードがすっきりします。
リトライ処理を実装する際は、ぜひRetryTemplateを活用してみてください。