重試的使用場景比較多,比如調用遠程服務時,由於網絡或者服務端響應慢導致調用超時,此時可以多重試幾次。用定時任務也可以實現重試的效果,但比較麻煩,用Spring Retry的話一個註解搞定所有。話不多說,先看演示。
首先引入依賴
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
使用方式有兩種:命令式和聲明式
命令式
/**
* 命令式的方式使用Spring Retry
*/
@GetMapping("/hello")
public String hello(@RequestParam("code") Integer code) throws Throwable {
RetryTemplate retryTemplate = RetryTemplate.builder()
.maxAttempts(3)
.fixedBackoff(1000)
.retryOn(RemoteAccessException.class)
.build();
retryTemplate.registerListener(new MyRetryListener());
String resp = retryTemplate.execute(new RetryCallback<String, Throwable>() {
@Override
public String doWithRetry(RetryContext context) throws Throwable {
return helloService.hello(code);
}
});
return resp;
}
定義一個RetryTemplate,然後調用execute方法,可配置項比較多,不一一列舉
真正使用的時候RetryTemplate可以定義成一個Bean,例如:
@Configuration
public class RetryConfig {
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = RetryTemplate.builder()
.maxAttempts(3)
.fixedBackoff(1000)
.withListener(new MyRetryListener())
.retryOn(RemoteAccessException.class)
.build();
return retryTemplate;
}
}
業務代碼:
/**
* 命令式的方式使用Spring Retry
*/
@Override
public String hello(int code) {
if (0 == code) {
System.out.println("出錯了");
throw new RemoteAccessException("出錯了");
}
System.out.println("處理完成");
return "ok";
}
監聽器實現:
package com.example.retry.listener;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
public class MyRetryListener implements RetryListener {
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
System.out.println("open");
return true;
}
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
System.out.println("close");
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
System.out.println("error");
}
}
聲明式(註解方式)
/**
* 註解的方式使用Spring Retry
*/
@Retryable(value = Exception.class, maxAttempts = 2, backoff = @Backoff(value = 1000, delay = 2000, multiplier = 0.5))
@Override
public String hi(int code) {
System.out.println("方法被調用");
int a = 1/code;
return "ok";
}
@Recover
public String hiRecover(Exception ex, int code) {
System.out.println("重試結束");
return "asdf";
}
這裏需要主要的幾點
- @EnableRetry(proxyTargetClass = true/false)
- @Retryable 修飾的方法必須是public的,而且不能是同一個類中調用
- @Recover 修飾的方法簽名必須與@Retryable修飾的方法一樣,除了第一個參數外
/**
* 註解的方式使用Spring Retry
*/
@GetMapping("/hi")
public String hi(@RequestParam("code") Integer code) {
return helloService.hi(code);
}
1. 用法
聲明式
@Configuration
@EnableRetry
public class Application {
}
@Service
class Service {
@Retryable(RemoteAccessException.class)
public void service() {
// ... do something
}
@Recover
public void recover(RemoteAccessException e) {
// ... panic
}
}
命令式
RetryTemplate template = RetryTemplate.builder()
.maxAttempts(3)
.fixedBackoff(1000)
.retryOn(RemoteAccessException.class)
.build();
template.execute(ctx -> {
// ... do something
});
2. RetryTemplate
爲了自動重試,Spring Retry 提供了 RetryOperations 重試操作策略
public interface RetryOperations {
<T> T execute(RetryCallback<T> retryCallback) throws Exception;
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback)
throws Exception;
<T> T execute(RetryCallback<T> retryCallback, RetryState retryState)
throws Exception, ExhaustedRetryException;
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback,
RetryState retryState) throws Exception;
}
基本回調是一個簡單的接口,允許插入一些要重試的業務邏輯:
public interface RetryCallback<T> {
T doWithRetry(RetryContext context) throws Throwable;
}
回調函數被嘗試,如果失敗(通過拋出異常),它將被重試,直到成功或實現決定中止。
RetryOperations最簡單的通用實現是RetryTemplate
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;
}
});
從Spring Retry 1.3開始,RetryTemplate支持流式配置:
RetryTemplate.builder()
.maxAttempts(10)
.exponentialBackoff(100, 2, 10000)
.retryOn(IOException.class)
.traversingCauses()
.build();
RetryTemplate.builder()
.fixedBackoff(10)
.withinMillis(3000)
.build();
RetryTemplate.builder()
.infiniteRetry()
.retryOn(IOException.class)
.uniformRandomBackoff(1000, 3000)
.build();
3. RecoveryCallback
當重試耗盡時,RetryOperations可以將控制傳遞給不同的回調:RecoveryCallback。
Foo foo = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// business logic here
},
new RecoveryCallback<Foo>() {
Foo recover(RetryContext context) throws Exception {
// recover logic here
}
});
4. Listeners
public interface RetryListener {
void open(RetryContext context, RetryCallback<T> callback);
void onSuccess(RetryContext context, T result);
void onError(RetryContext context, RetryCallback<T> callback, Throwable e);
void close(RetryContext context, RetryCallback<T> callback, Throwable e);
}
在最簡單的情況下,open和close回調在整個重試之前和之後,onSuccess和onError應用於個別的RetryCallback調用,onSuccess方法在成功調用回調之後被調用。
5. 聲明式重試
有時,你希望在每次業務處理髮生時都重試一些業務處理。這方面的典型例子是遠程服務調用。Spring Retry提供了一個AOP攔截器,它將方法調用封裝在RetryOperations實例中。RetryOperationsInterceptor執行被攔截的方法,並根據提供的RepeatTemplate中的RetryPolicy在失敗時重試。
你可以在 @Configuration 類上添加一個 @EnableRetry 註解,並且在你想要進行重試的方法(或者類)上添加 @Retryable 註解,還可以指定任意數量的重試監聽器。
@Configuration
@EnableRetry
public class Application {
@Bean
public Service service() {
return new Service();
}
@Bean public RetryListener retryListener1() {
return new RetryListener() {...}
}
@Bean public RetryListener retryListener2() {
return new RetryListener() {...}
}
}
@Service
class Service {
@Retryable(RemoteAccessException.class)
public service() {
// ... do something
}
}
可以使用 @Retryable 的屬性類控制 RetryPolicy 和 BackoffPolicy
@Service
class Service {
@Retryable(maxAttempts=12, backoff=@Backoff(delay=100, maxDelay=500))
public service() {
// ... do something
}
}
如果希望在重試用盡時採用替代代碼返回,則可以提供恢復方法。方法應該聲明在與@Retryable實例相同的類中,並標記爲@Recover。返回類型必須匹配@Retryable方法。恢復方法的參數可以包括拋出的異常和(可選地)傳遞給原始可重試方法的參數(或者它們的部分列表,只要在需要的最後一個之前不省略任何參數)。
@Service
class Service {
@Retryable(RemoteAccessException.class)
public void service(String str1, String str2) {
// ... do something
}
@Recover
public void recover(RemoteAccessException e, String str1, String str2) {
// ... error handling making use of original args if required
}
}
若要解決可選擇用於恢復的多個方法之間的衝突,可以顯式指定恢復方法名稱。
@Service
class Service {
@Retryable(recover = "service1Recover", value = RemoteAccessException.class)
public void service1(String str1, String str2) {
// ... do something
}
@Retryable(recover = "service2Recover", value = RemoteAccessException.class)
public void service2(String str1, String str2) {
// ... do something
}
@Recover
public void service1Recover(RemoteAccessException e, String str1, String str2) {
// ... error handling making use of original args if required
}
@Recover
public void service2Recover(RemoteAccessException e, String str1, String str2) {
// ... error handling making use of original args if required
}
}
https://github.com/spring-projects/spring-retry