--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
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'
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')
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);
}
}
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 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.");
}
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");
}
}
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;
}
}