本文是对前一篇文章关于请求异常处理(点击查看)的补充。有时当我们调用一个接口可能由于网络等原因造成第一次请求失败,如果再去尝试可能就成功了,这就是重试机制。下面演示如何结合 Spring Retry 实现请求发生异常时自动进行重试(重新发起请求)。
十一、请求异常自动重试
1,安装配置
(1)编辑项目 pom.xml 文件,添加 Spring Retry 相关依赖。
1 2 3 4 5 6 7 8 9 10 | <!-- 重试机制 --> <dependency> <groupId>org.springframework.retry< /groupId > <artifactId>spring-retry< /artifactId > <version>1.1.2.RELEASE< /version > < /dependency > <dependency> <groupId>org.aspectj< /groupId > <artifactId>aspectjweaver< /artifactId > < /dependency > |
(2)在主类上加入 @EnableRetry 注解,启用重试功能。
1 2 3 4 5 6 7 8 9 | @SpringBootApplication @EnableRetry public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(DemoApplication. class , args); } } |
2,使用样例
(1)由于这是前一篇文章关于请求异常处理的补充,首先我同样要创建一个自己的异常处理控制器(RestThrowErrorHandler)并在 RestTemplate 配置类中进行配置。目的是让 4XX、5XX 这样的请求也能成功返回到客户端。具体代码参考之前的文章:
(2)首先修改前文的 Service 类,在需要重试的方法上添加 @Retryable 和 @Backoff 注解,使其在发生异常时能够自动重试。
(1)@Retryable 注解的方法在发生异常时会重试,参数说明:
- value:当指定异常发生时会进行重试
- include:和 value 一样,默认空。如果 exclude 也为空时,所有异常都重试
- exclude:指定异常不重试,默认空。如果 include 也为空时,所有异常都重试
- maxAttemps:最大重试次数,默认 3
- backoff:重试等待策略,默认没有
(2)@Backoff 注解为重试等待策略,参数说明:
- delay:指定重试的延时时间,默认为 1000L
- multiplier:指定延迟的倍数,默认为 0。比如 delay=5000l,multiplier=2 时,第一次重试为 5 秒后,第二次为 10 秒,第三次为 20 秒。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @Service public class UserService { @Autowired private RestTemplate restTemplate; @Retryable (value = RestClientException. class , maxAttempts = 3 , backoff = @Backoff (delay = 5000l,multiplier = 1 )) public String getInfo() { ResponseEntity<string> responseEntity = restTemplate.getForEntity(url, String. class ); // 判断请求是否发生异常 if (!responseEntity.getStatusCode().is2xxSuccessful()){ System.out.println( "请求失败..." ); // 抛出异常 throw new RestClientException(responseEntity.getBody()); } // 没有异常的话则返回正常的响应结果 return responseEntity.getBody(); } }</string> |
(2)然后 Contoller 会调用这个 Service,这边代码同前文一样:
注意:由于 retry 用到了 aspect 增强,所以会有 aspect 的坑,就是方法内部调用,会使 aspect 增强失效,那么 retry 当然也会失效。
- 比如这里重试方法是定义在 Service 类里面,Controller 调用 Service 的这个方法,重试机制是没问题的。
- 但如果重试方法直接定义在这个 Controller 里面,也就同一个类里面内部调用,那么重试机制就会失效。
1 2 3 4 5 6 7 8 9 10 11 | @RestController public class HelloController { @Autowired private UserService userService; @GetMapping ( "/test" ) public String test() { return userService.getInfo(); } } |
(3)全局的异常处理类和前文一样,当超过重试次数是异常会被抛出,这个全局的异常处理类会捕获这个异常,并返回给前端处理的结果。
1 2 3 4 5 6 7 8 | @ControllerAdvice public class CustomExceptionHandler { @ExceptionHandler (RestClientException. class ) public ResponseEntity<string> throwRestException(RestClientException restClientException){ return new ResponseEntity<string>(restClientException.getMessage(), HttpStatus.BAD_REQUEST); } }</string></string> |
(4)测试一下,由于我们使用 RestTemplate 请求一个不存在的接口,可以看到 UserService 方法重复执行3次(每次间隔5秒)。
(5)超过重试次数后异常信息才返回到前端页面。
附:同时指定多个异常
@Retryable 注解的 value 属性可以同时设置多个异常类型,只要其中某个异常发生时,被注解的方法就会进行重试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @Service public class UserService { @Autowired RestTemplate restTemplate; @Retryable (value = {RestClientException. class , ConnectException. class }, maxAttempts = 3 , backoff = @Backoff (delay = 5000l,multiplier = 1 )) public String getInfo() { ResponseEntity<string> responseEntity = restTemplate.getForEntity(url, String. class ); // 判断请求是否发生异常 if (!responseEntity.getStatusCode().is2xxSuccessful()){ System.out.println( "请求失败..." ); // 抛出异常 throw new RestClientException(responseEntity.getBody()); } // 没有异常的话则返回正常的响应结果 return responseEntity.getBody(); } }</string> |