[JAVA] Spring Retry Troubleshooting

Overview

--List the countermeasures for various troubles that occur in Spring Retry --This environment: Java 8 + Spring Boot 2.2.0 + Spring Retry 1.2.4

Can't compile

The following error message is output.

Error:(3, 44) java:Package org.springframework.retry.annotation does not exist
Error:(11, 4) java:Can't find symbol
symbol:Class Retryable

It seems that Spring Retry has not been installed, so you can install Spring Retry.

For Apache Maven, add the following to the dependencies element of pom.xml.

<!-- 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>

For Gradle, add the following to the dependencies of build.gradle.

build.gradle


implementation 'org.springframework.retry:spring-retry:1.2.4.RELEASE'

Cannot be executed (BeanCreationException occurs)

The following error occurs.

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

AspectJ aspectjweaver is required when running Spring Retry. If you're using Spring Boot, it's a good idea to install Spring Boot AOP Starter.

For Apache Maven, add the following to the dependencies element of pom.xml.

<!-- 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>

For Gradle, add the following to the dependencies of build.gradle.

build.gradle


runtime('org.springframework.boot:spring-boot-starter-aop')

Will not be retried

Specify @EnableRetry

In order to use Spring Retry, it is necessary to specify @EnableRetry annotation in application class etc. as follows.

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);
  }
}

Specify @Service @Repository @Component etc.

Classes not registered in the DI container will not be retried. Specify annotation such as @Service @Repository @Component in the class to be retried as follows.

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;
  }
}

Specify @Retryable

Specify the type of the exception to be retried with the @Retryable annotation in the method with value or include (value can be used as an alias of include) as follows. It is also possible to specify the type of exception that is not subject to retry with 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.");
}

Specify @Autowired

Classes that are not registered in the DI container will not be retried, so specify the @Autowired annotation as shown below where you want to use them.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class DemoController {

  //To the object of the class where the retry process is written@Specify Autowired
  @Autowired
  private MyRetryableService service;

  @RequestMapping("/")
  public String index() {
    return service.hello("hello", "goodbye");
  }
}

Method with @Recover is not called (ExhaustedRetryException occurs)

The following error occurs.

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)

It is necessary to match the method with @Recover annotation to the method signature to be retried. Specifically, the "return value type" and the "retry target exception type" are described as arguments as follows.

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 {

  //Return value is String
  //The exception is 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.");
  }

  //Return value is String
  //Describe RuntimeException which is the exception to be retried in the argument
  @Recover
  public String recover(RuntimeException e) {
    throw e;
  }
}

Also, the argument of the original method does not have to exist in the method for which @Recover is specified, but by adding it, the value passed to the original method is passed as an argument, so information can be obtained.

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 {

  //Return value is String
  //The exception is RuntimeException
  //The arguments are String foo and 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.");
  }

  //Return value is String
  //Describe RuntimeException which is the exception to be retried in the argument
  //Add String foo and String bar of retry target method arguments after the exception argument
  @Recover
  public String recover(RuntimeException e, String foo, String bar) {
    System.out.println("foo=" + foo);
    System.out.println("bar=" + bar);
    throw e;
  }
}

Reference material

Recommended Posts

Spring Retry Troubleshooting
Implement declarative retry processing using Spring Retry
Application troubleshooting
spring × docker
About Spring ③
Spring Java