--Check the behavior with the sample program by Spring Boot + Spring Retry -Specify the exception to be retried with include in @Retryable annotation -Specify exceptions that are not retry targets with @Retryable annotation with exclude -Check that ExhaustedRetryException occurs when there is an exception that cannot be caught by @Recover.
├── pom.xml
└── src
└── main
└── java
└── info
└── maigo
└── lab
└── sample
└── retry
└── exhausted
├── HogeApplication.java
├── HogeController.java
├── HogeException.java
├── HogeHogeException.java
└── HogeService.java
This time, run the build with Maven. pom.xml is based on the one generated by Spring Initializr. Add spring-retry to dependencies to use Spring Retry. Also, add spring-boot-starter-aop because AOP class is required at runtime.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.M4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>info.maigo.lab</groupId>
<artifactId>sample.retry.exhausted</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sample.retry.exhausted</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 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>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
</project>
Generate a JAR file with the mvn package command.
$ mvn package
Start the server by specifying the JAR file with the java command.
$ java -jar target/sample.retry.exhausted-0.0.1-SNAPSHOT.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.0.M4)
HogeApplication.java
Boot entry class by Spring Boot. The @EnableRetry annotation is specified to use Spring Retry.
package info.maigo.lab.sample.retry.exhausted;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;
@SpringBootApplication
@EnableRetry
public class HogeApplication {
public static void main(String[] args) {
SpringApplication.run(HogeApplication.class, args);
}
}
HogeController.java
A Controller class that receives an HTTP request and returns a response. When an exception occurs, handle it with the method that specifies the @ExceptionHandler annotation.
package info.maigo.lab.sample.retry.exhausted;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HogeController {
@Autowired
private HogeService hogeService;
@RequestMapping("/")
public Map<String, Object> index(@RequestParam(name = "name", defaultValue = "java.lang.RuntimeException") String name) throws Exception {
return new HashMap<String, Object>() {
{
put("result", hogeService.invoke(name));
}
};
}
@ExceptionHandler(Exception.class)
public Map<String, Object> handleException(HttpServletRequest req, Exception e) {
return new HashMap<String, Object>() {
{
put("handleException", e.getClass().getName() + " / " + e.getMessage());
}
};
}
}
HogeException.java
An exception class that just inherits from Exception.
package info.maigo.lab.sample.retry.exhausted;
public class HogeException extends Exception {
}
HogeHogeException.java
An exception class that inherits from HogeException.
package info.maigo.lab.sample.retry.exhausted;
public class HogeHogeException extends HogeException {
}
HogeService.java
Service class. In the invoke method, an exception object is created and thrown based on the specified character string. The @Retryable annotation is specified in the invoke method. Include specifies the type of exception to be retried, and exclude specifies the type of exception not to be retried. Specify the number of retries with maxAttempts. Specify the waiting time until retry with backoff. The method with @Recover annotation is called when an exception occurs even after trying the specified number of times (even if the exception is not the target of retry, this method is called if it matches the type).
package info.maigo.lab.sample.retry.exhausted;
import java.lang.reflect.Constructor;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.retry.annotation.Recover;
@Service
public class HogeService {
@Retryable(
include = {HogeException.class},
exclude = {HogeHogeException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000))
public String invoke(String name) throws Exception {
System.out.println("HogeService#invoke: " + name);
Class cls = Class.forName(name);
Constructor cnst = cls.getDeclaredConstructor();
Exception e = (Exception) cnst.newInstance();
throw e;
}
@Recover
public String recover(HogeException e, String name) {
System.out.println("HogeService#recover: " + name);
return "HogeService#recover: " + e.getClass().getName();
}
}
JSON is output when accessing the started server with curl. Specifies that a HogeException should occur. The recover method returns a string representation of HogeException.
$ curl http://localhost:8080/?name=info.maigo.lab.sample.retry.exhausted.HogeException
{"result":"HogeService#recover: info.maigo.lab.sample.retry.exhausted.HogeException"}
View the standard output log on the server side. You can see that the recover method is called after the retry is executed and the invoke method is called three times.
HogeService#invoke: info.maigo.lab.sample.retry.exhausted.HogeException
HogeService#invoke: info.maigo.lab.sample.retry.exhausted.HogeException
HogeService#invoke: info.maigo.lab.sample.retry.exhausted.HogeException
HogeService#recover: info.maigo.lab.sample.retry.exhausted.HogeException
Since HogeException is specified in include of @Retryable annotation, retry processing is executed. If an exception occurs in the last retry, the method with @Recover annotation is called.
Access with curl by specifying that HogeHogeException will occur. The recover method returns a string representation of HogeHogeException.
$ curl http://localhost:8080/?name=info.maigo.lab.sample.retry.exhausted.HogeHogeException
{"result":"HogeService#recover: info.maigo.lab.sample.retry.exhausted.HogeHogeException"}
View the standard output log on the server side. You can see that the recover method is called after the invoke method is called only once.
HogeService#invoke: info.maigo.lab.sample.retry.exhausted.HogeHogeException
HogeService#recover: info.maigo.lab.sample.retry.exhausted.HogeHogeException
HogeHogeException is specified in exclude of @Retryable annotation, so retry processing is not performed. I called the invoke method once and got a HogeHogeException, and the recover method was called and the resulting string was returned. Also, HogeHogeException is a subclass of HogeException specified by include of @Retryable annotation, so if HogeHogeException is not specified in exclude, it will be retried.
Access with curl by specifying that java.lang.Exception will occur. An org.springframework.retry.ExhaustedRetryException exception is thrown without being caught by the recover method.
$ curl http://localhost:8080/?name=java.lang.Exception
{"handleException":"org.springframework.retry.ExhaustedRetryException / Cannot locate recovery method; nested exception is java.lang.Exception"}
View the standard output log on the server side. You can see that the invoke method is called only once. The recover method has not been called.
HogeService#invoke: java.lang.Exception
Since java.lang.Exception is not specified in include of @Retryable annotation, retry processing is not performed. Also, ExhaustedRetryException is thrown because the @Recover annotation is not the type of exception that can be caught by the specified method. In this case, the getCause method of the ExhaustedRetryException class can be used to get the object of the exception java.lang.Exception that caused it.
Recommended Posts