概述
- 通过Spring Boot Spring Retry使用示例程序检查行为
-
指定包含在@Retryable批注中的重试异常
-
使用@Retryable注释(带有排除项)指定不是重试目标的异常
-
确认存在@Recover无法捕获的异常时,发生ExhaustedRetryException
示例程序
的源代码列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ├── pom.xml └── src └── main └── java └── info └── maigo └── lab └── sample └── retry └── exhausted ├── HogeApplication.java ├── HogeController.java ├── HogeException.java ├── HogeHogeException.java └── HogeService.java |
运行检查环境
- macOS Mojave
- OpenJDK 11.0.2
- Spring Boot 2.2.0 M4
- 春季重试1.2.4
Maven构建文件pom.xml
这次,使用Maven运行构建。
pom.xml基于Spring Initializr生成的那个。
将spring-retry添加到依赖项以使用Spring Retry。
另外,添加spring-boot-starter-aop,因为在运行时需要AOP类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | <?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> |
生成并启动服务器
使用
mvn package命令生成一个JAR文件。
1 | $ mvn package |
通过使用
java命令指定JAR文件来启动服务器。
1 2 3 4 5 6 7 8 9 | $ java -jar target/sample.retry.exhausted-0.0.1-SNAPSHOT.jar . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.2.0.M4) |
源代码
HogeApplication.java
Spring Boot启动入口类。
指定@EnableRetry批注以使用Spring Retry。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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
接收HTTP请求并返回响应的控制器类。
发生异常时,请使用指定@ExceptionHandler批注的方法对其进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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
仅从
异常继承的异常类。
1 2 3 4 | package info.maigo.lab.sample.retry.exhausted; public class HogeException extends Exception { } |
HogeHogeException.java
从HogeException继承的异常类。
1 2 3 4 | package info.maigo.lab.sample.retry.exhausted; public class HogeHogeException extends HogeException { } |
HogeService.java
服务类别。
在invoke方法中,将基于指定的字符串创建并抛出异常对象。
@Retryable批注在invoke方法中指定。包含指定要重试的异常类型,而排除指定不重试的异常类型。使用maxAttempts指定重试次数。指定等待直到重试退避的等待时间。
即使尝试指定的次数后发生异常,也会调用带有@Recover批注的方法(即使该异常不是重试的目标,但如果与类型匹配,也会调用此方法)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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(); } } |
执行示例
引发HogeException并重试
使用curl访问启动的服务器时,将输出JSON。
指定应发生HogeException。
恢复方法返回HogeException的字符串表示形式。
1 2 | $ curl http://localhost:8080/?name=info.maigo.lab.sample.retry.exhausted.HogeException {"result":"HogeService#recover: info.maigo.lab.sample.retry.exhausted.HogeException"} |
在服务器端查看标准输出日志。
您可以看到执行重试之后将调用restore方法,并且调用方法被调用了3次。
1 2 3 4 | 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 |
由于
HogeException是在@Retryable注释的include中指定的,因此将执行重试处理。
如果在上一次重试中发生异常,则将调用带有@Recover批注的方法。
不要导致HogeHogeException并重试
指定发生HogeHogeException并使用curl访问。
恢复方法返回HogeHogeException的字符串表示形式。
1 2 | $ curl http://localhost:8080/?name=info.maigo.lab.sample.retry.exhausted.HogeHogeException {"result":"HogeService#recover: info.maigo.lab.sample.retry.exhausted.HogeHogeException"} |
在服务器端查看标准输出日志。
您可以看到在invoke方法仅被调用一次之后,recover方法被调用了。
1 2 | HogeService#invoke: info.maigo.lab.sample.retry.exhausted.HogeHogeException HogeService#recover: info.maigo.lab.sample.retry.exhausted.HogeHogeException |
HogeHogeException在@Retryable注释的排除中指定,因此不执行重试处理。
我一次调用了invoke方法,并获得了HogeHogeException,然后调用了restore方法,并返回了结果字符串。
另外,HogeHogeException是@Retryable注释的include中指定的HogeException的子类,因此,如果未在exclude中指定HogeHogeException,则将重试。
@Recover引发无法捕获的异常时,将发生ExhaustedRetryException
通过指定
java.lang.Exception来进行curl访问。
抛出org.springframework.retry.ExhaustedRetryException异常,而没有被restore方法捕获。
1 2 | $ curl http://localhost:8080/?name=java.lang.Exception {"handleException":"org.springframework.retry.ExhaustedRetryException / Cannot locate recovery method; nested exception is java.lang.Exception"} |
在服务器端查看标准输出日志。
您可以看到invoke方法仅被调用一次。恢复方法尚未被调用。
1 | HogeService#invoke: java.lang.Exception |
java.lang.Exception未在@Retryable注释的include中指定,因此不执行重试处理。
另外,抛出ExhaustedRetryException,因为@Recover注释不是指定方法可以捕获的异常类型。
在这种情况下,可以使用ExhaustedRetryException类的getCause方法获取导致它的异常java.lang.Exception的对象。
参考
- GitHub --spring-projects / spring-retry
- org.springframework.retry:spring-retry:1.2.4.RELEASE API Doc :: Javadoc.IO
- 可重试(Spring Retry 1.2.4.RELEASE API)
- ExhaustedRetryException(Spring Retry 1.2.4.RELEASE API)
- @Retryable exclude选项无法按预期运行·问题#47·spring-projects / spring-retry·GitHub
- 使用Spring Retry --Qiita实现声明性重试处理