前言
在实际工作中,重处理是一个非常常见的场景,比如:
发送消息失败。
调用远程服务失败。
争抢锁失败。
这些错误可能是因为网络波动造成的,等待过后重处理就能成功。通常来说,会用try/catch,while循环之类的语法来进行重处理,但是这样的做法缺乏统一性,并且不是很方便,要多写很多代码。然而spring-retry却可以通过注解,在不入侵原有业务逻辑代码的方式下,优雅的实现重处理功能。
一、@Retryable是什么?
spring系列的spring-retry是另一个实用程序模块,可以帮助我们以标准方式处理任何特定操作的重试。在spring-retry中,所有配置都是基于简单注释的。
二、使用步骤
1.pom依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.5.RELEASE</version>
</dependency>
2 在服务启动类上加注解 @EnableRetry**
@EnableRetry
@SpringBootApplication
public class HelloApplication {
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
}
3接下来就是在需要重试的方法上面加注解@Retryable,需要注意的是由于是基于AOP实现,所以不支持类里自调用方法,一般把该重试方法写成一个service,通过service调用,不然重试不会生效
pscInvInfoService.ocrVerify(json);
下面是具体的方法
@Override
@Retryable(value = Exception.class, maxAttempts = 3, backoff =
@Backoff(delay = 2000L, multiplier = 1.5))
public void ocrVerify(String jsonStr) throws Exception {
// 该重试只针对网络问题导致的错误重试,不对发票验真失败的进行重试
log.info("jsonStr" + jsonStr);
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, jsonStr);
Request request = new Request.Builder()
.url(verifyUrl)
.method("POST", body)
.addHeader("Content-Type", "application/json")
.build();
Response response = client.newCall(request).execute();
JSONObject result = JSONObject.parseObject(response.body().string());
JSONObject S = JSONObject.parseObject(result.get("returnStateInfo").toString());
String status = S.getString("returnCode");
String msg = S.getString("returnMessage");
String data = result.getString("data");
if (POST_SUCCESS.equals(status)) {
log.info("成功" + data);
// 修改状态
JSONObject js = JSONObject.parseObject(jsonStr);
String invCode = js.getString("invoiceCode");
String invNo = js.getString("invoiceNo");
Date date = js.getDate("invoiceDate");
pscInvInfoMapper.updateVerifyStatus(invNo, invCode, date);
log.info("修改状态-成功"+DateTimeUtils.getCurrentDateTime(DateTimeUtils.DATETIME_PATTERN));
} else {
log.info("失败" + msg);
}
}
3.1 Retryable注解的参数具体含义
value:抛出指定异常才会重试
include:和value一样,默认为空,当exclude也为空时,默认所有异常
exclude:指定不处理的异常
maxAttempts:最大重试次数,默认3次
backoff:重试等待策略,默认使用@Backoff,@Backoff的value默认为1000L,我们设置为2000L;multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒。
三 当重试耗尽时还是失败,会出现什么情况呢?
当重试耗尽时,RetryOperations可以将控制传递给另一个回调,即RecoveryCallback。Spring-Retry还提供了@Recover注解,用于@Retryable重试失败后处理方法。如果不需要回调方法,可以直接不写回调方法,那么实现的效果是,重试次数完了后,如果还是没成功没符合业务判断,就抛出异常。
@Recover
public void verifyRecover(Exception e){
System.out.println("=======重试次数到达最大值,重试结束===="+e.getMessage());
}
可以看到传参里面写的是 Exception e,这个是作为回调的接头暗号(重试次数用完了,还是失败,我们抛出这个Exception e通知触发这个回调方法)。对于@Recover注解的方法,需要特别注意的是:
方法的返回值必须与@Retryable方法一致
方法的第一个参数,必须是Throwable类型的,建议是与@Retryable配置的异常一致,其他的参数,需要哪个参数,写进去就可以了(@Recover方法中有的)
该回调方法与重试方法写在同一个实现类里面
四 注意事项
1.由于是基于AOP实现,所以不支持类里自调用方法
2.如果重试失败需要给@Recover注解的方法做后续处理,那这个重试的方法不能有返回值,只能是void
3.方法内不能使用try catch,只能往外抛异常
4.@Recover注解来开启重试失败后调用的方法(注意,需跟重处理方法在同一个类中),此注解注释的方法参数一定要是@Retryable抛出的异常,否则无法识别,可以在该方法中进行日志处理。
五 重试场景
可以看到重试了三次并且最终执行了失败处理方法