概要
- Spring Retry で発生する様々なトラブルについての対応策を列挙する
- 今回の環境: Java 8 + Spring Boot 2.2.0 + Spring Retry 1.2.4
コンパイルできない
以下のようなエラーメッセージが出力される。
Error:(3, 44) java: パッケージorg.springframework.retry.annotationは存在しません
Error:(11, 4) java: シンボルを見つけられません
シンボル: クラス Retryable
Spring Retry が導入されていないと思われるため、Spring Retry を導入すれば良い。
Apache Maven の場合は pom.xml の dependencies 要素に以下を追加する。
<!-- https://mvnrepository.com/artifact/org.springframework.retry/spring-retry -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.4.RELEASE</version>
</dependency>
Gradle の場合は build.gradle の dependencies に以下を追加する。
implementation 'org.springframework.retry:spring-retry:1.2.4.RELEASE'
実行できない (BeanCreationException が発生する)
以下のようなエラーが発生する。
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.aop.config.internalAutoProxyCreator': Initialization of bean failed; nested exception is java.lang.NoClassDefFoundError: org/aspectj/lang/annotation/Pointcut
Caused by: java.lang.NoClassDefFoundError: org/aspectj/lang/annotation/Pointcut
Caused by: java.lang.ClassNotFoundException: org.aspectj.lang.annotation.Pointcut
Spring Retry の実行時には AspectJ の aspectjweaver が必要。
Spring Boot を使っているなら Spring Boot AOP Starter を導入するのが良い。
Apache Maven の場合は pom.xml の dependencies 要素に以下を追加する。
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<scope>runtime</scope>
</dependency>
Gradle の場合は build.gradle の dependencies に以下を追加する。
runtime('org.springframework.boot:spring-boot-starter-aop')
リトライされない
@EnableRetry を指定する
Spring Retry を使うためには以下のようにアプリケーションクラス等に @EnableRetry アノテーションを指定する必要がある。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;
@SpringBootApplication
@EnableRetry
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@Service @Repository @Component などを指定する
DI コンテナに登録していないクラスはリトライ対象にならない。
以下のようにリトライ処理するクラスに @Service @Repository @Component などのアノテーションを指定する。
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
@Service
public class DemoService {
@Retryable(
value = {RuntimeException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000))
public String hello(String foo, String bar) {
throw new RuntimeException("This is a sample error.");
}
@Recover
public String recover(RuntimeException e, String foo, String bar) {
throw e;
}
}
@Retryable を指定する
以下のようにメソッドに @Retryable アノテーションでリトライする例外の型を value または include で指定する (value は include の別名として使用可能)。また、リトライ対象外の例外の型を exclude で指定することも可能。
@Retryable(
include = {Exception.class},
exclude = {RuntimeException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000))
public String hello(String foo, String bar) {
throw new RuntimeException("This is a sample error.");
}
@Retryable(
value = {Exception.class},
exclude = {RuntimeException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000))
public String hello(String foo, String bar) {
throw new RuntimeException("This is a sample error.");
}
@Autowired を指定する
DI コンテナに登録していないクラスはリトライ対象にならないため、使用する箇所では以下のように @Autowired アノテーションを指定する。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class DemoController {
// リトライ処理が書いてあるクラスのオブジェクトに @Autowired を指定
@Autowired
private MyRetryableService service;
@RequestMapping("/")
public String index() {
return service.hello("hello", "goodbye");
}
}
@Recover をつけたメソッドが呼ばれない (ExhaustedRetryException が発生する)
以下のようなエラーが発生する。
org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method; nested exception is java.lang.RuntimeException: This is a sample error.
at org.springframework.retry.annotation.RecoverAnnotationRecoveryHandler.recover(RecoverAnnotationRecoveryHandler.java:61)
at org.springframework.retry.interceptor.RetryOperationsInterceptor$ItemRecovererCallback.recover(RetryOperationsInterceptor.java:141)
at org.springframework.retry.support.RetryTemplate.handleRetryExhausted(RetryTemplate.java:512)
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:351)
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:180)
@Recover アノテーションを指定したメソッドを、リトライ対象のメソッドシグネチャに合わせる必要がある。
具体的には、以下のように「戻り値の型」と「リトライ対象の例外の型」を引数として記述する。
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
@Service
public class DemoService {
// 戻り値が String
// 例外が RuntimeException
@Retryable(
value = {RuntimeException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000))
public String hello(String foo, String bar) {
System.out.println("DemoService#hello");
throw new RuntimeException("This is a sample error.");
}
// 戻り値が String
// 引数にリトライ対象の例外である RuntimeException を記述
@Recover
public String recover(RuntimeException e) {
throw e;
}
}
また、元のメソッドの引数が @Recover を指定したメソッドには存在する必要はないが、付加することで元のメソッドに渡された値が引数として渡されるので情報を取得できるようになる。
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
@Service
public class DemoService {
// 戻り値が String
// 例外が RuntimeException
// 引数が String foo と String bar
@Retryable(
value = {RuntimeException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000))
public String hello(String foo, String bar) {
System.out.println("DemoService#hello");
throw new RuntimeException("This is a sample error.");
}
// 戻り値が String
// 引数にリトライ対象の例外である RuntimeException を記述
// 例外の引数のあとに、リトライ対象メソッド引数の String foo と String bar を追加
@Recover
public String recover(RuntimeException e, String foo, String bar) {
System.out.println("foo=" + foo);
System.out.println("bar=" + bar);
throw e;
}
}