简介
Spring Retry重试框架支持声明式(Declarative)和编程式(imperative)两种用法。
环境要求
Spring Retry requires Java 1.7 and Maven 3.0.5 (or greater).
建议java 1.8以上,使用lambda表达式简化代码。
声明式(Declarative)
声明式用法基于AOP切面编程,需要引入aspectjweaver或spring-boot-starter-aop
pom.xml
<!-- Spring Retry -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Configuration
@EnableRetry
public class Application {
}
@Service
class Service {
@Retryable(RemoteAccessException.class)
public void service() {
// ... do something
}
@Recover
public void recover(RemoteAccessException e) {
// ... panic
}
}
实际案例:
@Override
@Retryable(value = { Exception.class }, maxAttempts = 3, backoff = @Backoff(delay = 5000L, multiplier = 1))
public Object restPostTest(Map map, String url) throws Exception {
logger.info("调用第三方接口");
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
//设置访问的Entity
HttpEntity entity = new HttpEntity<>(map, headers);
ResponseEntity<String> result;
JSONObject data = new JSONObject();
//发起一个POST请求
logger.info("发起一个POST请求");
result = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
data = JSONObject.parseObject(result.getBody());
logger.info("返回结果{}",data);
return data;
}
@Recover
public Object recover(Exception e, Map map, String url) {
// ... 放入消息队列或数据库
}
@Retryable注解
被注解的方法发生异常时会重试
value:指定发生的异常进行重试
include:和value一样,默认空,当exclude也为空时,所有异常都重试
exclude:指定异常不重试,默认空,当include也为空时,所有异常都重试
maxAttemps:重试次数,默认3
backoff:重试补偿机制,默认没有
@Backoff注解
delay:指定延迟后重试
multiplier:指定延迟的倍数,比如delay=5000l,multiplier=2时,第一次重试为5秒后,第二次为10秒,第三次为20秒
@Recover
当重试到达指定次数时,被注解的方法将被回调,可以在该方法中进行日志处理。需要注意的是发生的异常和入参类型一致时才会回调。
说明:
- 以上注解所在的类对象必须由spring容器接管,使用@Service、@Bean等方式。
- @Retryable 和 @Recover标注的方法返回值必须保持一致!
- @Recover方法参数,Exception e必须放在第一个参数位置且类型需与@Retryable抛出类型一致。
- @Retryable方法内不可再使用try…catch捕获异常,防止异常无法被接收。
编程式(imperative)
RetryTemplate template = new RetryTemplate();
TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
policy.setTimeout(30000L);
template.setRetryPolicy(policy);
Foo result = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// Do stuff that might fail, e.g. webservice operation
return result;
}
});
链式用法结合lambda表达式简化代码
RetryTemplate template = RetryTemplate.builder()
.maxAttempts(3)
.fixedBackoff(1000)
.retryOn(RemoteAccessException.class)
.build();
template.execute(ctx -> {
// ... do something
});
lambda部分未重写RetryCallback#doWithRetry的逻辑。
public interface RetryCallback<T> {
T doWithRetry(RetryContext context) throws Throwable;
}
监听器:
public interface RetryListener {
void open(RetryContext context, RetryCallback<T> callback);
void onError(RetryContext context, RetryCallback<T> callback, Throwable e);
void close(RetryContext context, RetryCallback<T> callback, Throwable e);
}
template.registerListener(new MethodInvocationRetryListenerSupport() {
@Override
protected <T, E extends Throwable> void doClose(RetryContext context,
MethodInvocationRetryCallback<T, E> callback, Throwable throwable) {
monitoringTags.put(labelTagName, callback.getLabel());
Method method = callback.getInvocation()
.getMethod();
monitoringTags.put(classTagName,
method.getDeclaringClass().getSimpleName());
monitoringTags.put(methodTagName, method.getName());
// register a monitoring counter with appropriate tags
// ...
}
});