[JAVA] Check the behavior of include, exclude, ExhaustedRetryException of Spring Retry

Overview

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

Source code list of sample program

├── pom.xml
└── src
    └── main
        └── java
            └── info
                └── maigo
                    └── lab
                        └── sample
                            └── retry
                                └── exhausted
                                    ├── HogeApplication.java
                                    ├── HogeController.java
                                    ├── HogeException.java
                                    ├── HogeHogeException.java
                                    └── HogeService.java

Operation check environment

Maven build file pom.xml

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>

Build and start server

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)

Source code

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

Execution example

Raise HogeException and retry

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.

Do not cause HogeHogeException to retry

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.

ExhaustedRetryException occurs when @Recover throws an exception that cannot be caught

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.

Reference material

Recommended Posts

Check the behavior of include, exclude, ExhaustedRetryException of Spring Retry
[Java] [Spring] Test the behavior of the logger
Let's check the feel of Spring Boot + Swagger 2.0
Check the behavior of Java Intrinsic Locks with bpftrace
Check the behavior of getOne, findById, and query methods in Spring Boot + Spring Data JPA
About the behavior of ruby Hash # ==
[Rails] Check the contents of the object
Check the version of Cent OS
Part 4: Customize the behavior of OAuth 2.0 Login supported by Spring Security 5
Check the migration status of rails
See the behavior of entity update with Spring Boot + Spring Data JPA
Filter the result of BindingResult [Spring]
The story of encountering Spring custom annotation
Check the contents of params with pry
Memo: [Java] Check the contents of the directory
About the initial display of Spring Framework
Check the version of the standard Web software.
[Java] Check the number of occurrences of characters
Investigate the behavior of JPA transaction timeout
Check the operation of the interface through threads
Organize the differences in behavior of @NotBlank, @NotEmpty, @NotNull with Spring Boot + Thymeleaf
Check the version of the JDK installed and the version of the JDK enabled
About the official start guide of Spring Framework
The story of raising Spring Boot 1.5 series to 2.1 series
The official name of Spring MVC is Spring Web MVC