重試的使用場景比較多,比如呼叫遠端服務時,由於網路或者伺服器端響應慢導致呼叫超時,此時可以多重試幾次。用定時任務也可以實現重試的效果,但比較麻煩,用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";
}
這裡需要主要的幾點
/**
* 註解的方式使用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