This is a retry sample combining Spring Boot and Spring Retry.
This is a retry sample combining Spring Boot and Spring Retry. We introduce two types of samples: a declarative usage method with retry annotations and an operation from a program using RetryTemplate
.
- Java 17 or later
- Maven 3.6 or later
- Use
curl
to call the HTTP endpoint.
In addition to spring-boot-starter-web
, the following libraries are required. For details, please refer to pom.xml
.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
Build with the following command.
mvn clean package
Start the Spring Boot application with the following command.
mvn spring-boot:run
The following logs will be displayed and the application will start.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.4)
2021-09-15 17:54:34.609 INFO 560 --- [ main] c.e.r.springboot.SpringbootApplication : Starting SpringbootApplication using Java 17.0.11 on NICKEL with PID 560 (/work/waf-java-samples/retry/04-springboot/target/classes started by moris in /work/waf-java-samples/retry/04-springboot)
2021-09-15 17:54:34.614 INFO 560 --- [ main] c.e.r.springboot.SpringbootApplication : No active profile set, falling back to default profiles: default
2021-09-15 17:54:38.178 INFO 560 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-09-15 17:54:38.216 INFO 560 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-09-15 17:54:38.216 INFO 560 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.52]
2021-09-15 17:54:38.431 INFO 560 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-09-15 17:54:38.431 INFO 560 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 3731 ms
2021-09-15 17:54:39.749 INFO 560 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-09-15 17:54:39.772 INFO 560 --- [ main] c.e.r.springboot.SpringbootApplication : Started SpringbootApplication in 6.577 seconds (JVM running for 7.364)
If you want to change the port, please execute it by adding the following argument to the command line.
mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8888
Call the endpoint and check the retry behavior in the application log.
Call /hello1
.
curl http://localhost:8080/hello1?name=spring-boot
The instructions for the annotation are as follows:
- Maximum of 4 attempts (3 retries)
IOException
as the target for retry- Retry interval is 3 seconds × 2^(retry count)
@Retryable(
value = { IOException.class },
maxAttempts = 4,
backoff = @Backoff(delay = 3000, multiplier = 2))
The log when it fails to the end is as follows, and it is executed 4 times, and the retry intervals are 3 seconds, 6 seconds, 12 seconds, and 24 seconds, respectively.
2021-09-15 20:28:30.132 INFO 1228 --- [nio-8080-exec-3] c.e.retry.springboot.HelloController : hello1
2021-09-15 20:28:30.132 INFO 1228 --- [nio-8080-exec-3] c.e.r.springboot.services.HelloService : sayHello
2021-09-15 20:28:33.133 INFO 1228 --- [nio-8080-exec-3] c.e.r.springboot.services.HelloService : sayHello
2021-09-15 20:28:39.133 INFO 1228 --- [nio-8080-exec-3] c.e.r.springboot.services.HelloService : sayHello
2021-09-15 20:28:51.134 INFO 1228 --- [nio-8080-exec-3] c.e.r.springboot.services.HelloService : sayHello
There is a method that throws an exception in HelloService.java
. It throws an exception randomly, but if you want to change the behavior, please change it as appropriate. If you set it to 1.0
, an exception will always be thrown.
private static void someFunction() throws IOException {
double r = Math.random();
if (r < 0.7) {
throw new IOException("IO Error");
}
}
Call /hello2
.
curl http://localhost:8080/hello2?name=spring-boot
As specified in the annotation, the retry interval is executed at equal intervals every 2 seconds, with a maximum of 3 attempts.
@Retryable(
value = { IOException.class },
maxAttempts = 3,
backoff = @Backoff(delay = 2000))
You can see from the execution time in the log that it is executed every 2 seconds. Also, if it fails to the end, the method specified with the @Recover
annotation will be called.
2021-09-15 20:48:39.212 INFO 1393 --- [nio-8080-exec-8] c.e.r.s.s.HelloServiceWithRecover : sayHello
2021-09-15 20:48:41.212 INFO 1393 --- [nio-8080-exec-8] c.e.r.s.s.HelloServiceWithRecover : sayHello
2021-09-15 20:48:43.213 INFO 1393 --- [nio-8080-exec-8] c.e.r.s.s.HelloServiceWithRecover : sayHello
2021-09-15 20:48:43.213 INFO 1393 --- [nio-8080-exec-8] c.e.r.s.s.HelloServiceWithRecover : recover : spring-boot
Call /hello3
.
curl http://localhost:8080/hello3?name=spring-boot
Retry using the retry template configured in RetryTemplateConfig.java
. In addition, we register a custom listener and record logs.
As described in the program, the maximum number of attempts is 3, the initial interval is 2 seconds, and the retry interval is 2×2^(retry count).
2021-09-15 20:53:43.837 INFO 1504 --- [nio-8080-exec-1] c.e.retry.springboot.HelloController : hello3
2021-09-15 20:53:43.837 INFO 1504 --- [nio-8080-exec-1] c.e.r.s.s.HelloServiceWithRetryTemplate : sayHello
2021-09-15 20:53:43.858 INFO 1504 --- [nio-8080-exec-1] .RetryTemplateConfig$CustomRetryListener : open
2021-09-15 20:53:43.862 INFO 1504 --- [nio-8080-exec-1] c.e.r.s.s.HelloServiceWithRetryTemplate : retry count : 0
2021-09-15 20:53:43.863 INFO 1504 --- [nio-8080-exec-1] .RetryTemplateConfig$CustomRetryListener : onError, retry count : 1
2021-09-15 20:53:45.868 INFO 1504 --- [nio-8080-exec-1] c.e.r.s.s.HelloServiceWithRetryTemplate : retry count : 1
2021-09-15 20:53:45.868 INFO 1504 --- [nio-8080-exec-1] .RetryTemplateConfig$CustomRetryListener : onError, retry count : 2
2021-09-15 20:53:49.869 INFO 1504 --- [nio-8080-exec-1] c.e.r.s.s.HelloServiceWithRetryTemplate : retry count : 2
2021-09-15 20:53:49.869 INFO 1504 --- [nio-8080-exec-1] .RetryTemplateConfig$CustomRetryListener : onError, retry count : 3
2021-09-15 20:53:49.869 INFO 1504 --- [nio-8080-exec-1] .RetryTemplateConfig$CustomRetryListener : close
In the sample explanation, the parameters related to retry are hard-coded, but please avoid such implementation in production code.
Although it is not used in HelloService
, a sample that takes parameters from outside is implemented.
In the following example, it refers to the value from the property set externally (for example, application.yml
, environment variables, etc.). In this sample, it is defined in resources/application.yml
, but this location can also be changed. Since there are various setting methods, please refer to the reference for details.
@Retryable(
value = { IOException.class },
maxAttemptsExpression = "${my.retry.maxAttempts}",
backoff = @Backoff(delayExpression = "${my.retry.delay}",
multiplierExpression= "${my.retry.multiplier}"))
public String sayHello2(String name) throws IOException {
logger.info("sayHello2");
someFunction();
return String.format("Hello %s !!", name);
}
EOF