1. 程式人生 > 程式設計 >SpringBoot @Retryable註解方式

SpringBoot @Retryable註解方式

背景

在呼叫第三方介面或者使用MQ時,會出現網路抖動,連線超時等網路異常,所以需要重試。為了使處理更加健壯並且不太容易出現故障,後續的嘗試操作,有時候會幫助失敗的操作最後執行成功。一般情況下,需要我們自行實現重試機制,一般是在業務程式碼中加入一層迴圈,如果失敗後,再嘗試重試,但是這樣實現並不優雅。在SpringBoot中,已經實現了相關的能力,通過@Retryable註解可以實現我們想要的結果。

@Retryable

首先來看一下Spring官方文件的解釋:

SpringBoot @Retryable註解方式

@Retryable註解可以註解於方法上,來實現方法的重試機制。

POM依賴

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
 <groupId>org.springframework.retry</groupId>
 <artifactId>spring-retry</artifactId>
</dependency>

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

使用例項

SpringBoot retry的機制比較簡單,只需要兩個註解即可實現。

啟動類

@SpringBootApplication
@EnableRetry
public class Application {
 public static void main(String[] args) {
  SpringApplication.run(Application.class,args);
 }
}

在啟動類上,需要加入@EnableRetry註解,來開啟重試機制。

Service類

前面提到過,@Retryable是基於方法級別的,因此在Service中,需要在你希望重試的方法上,增加重試註解。

@Service
@Slf4j
public class DoRetryService {

 @Retryable(value = Exception.class,maxAttempts = 4,backoff = @Backoff(delay = 2000L,multiplier = 1.5))
 public boolean doRetry(boolean isRetry) throws Exception {
  log.info("開始通知下游系統");
  log.info("通知下游系統");
  if (isRetry) {
   throw new RuntimeException("通知下游系統異常");
  }
  return true;
 }
}

來簡單解釋一下註解中幾個引數的含義:

名稱 含義
interceptor Retry interceptor bean name to be applied for retryable method.
value Exception types that are retryable. Synonym for includes(). Defaults to empty (and if excludes is also empty all exceptions are retried).
include Exception types that are retryable. Defaults to empty (and if excludes is also empty all exceptions are retried).
exclude Exception types that are not retryable. Defaults to empty (and if includes is also empty all exceptions are retried).
label A unique label for statistics reporting. If not provided the caller may choose to ignore it,or provide a default.
stateful Flag to say that the retry is stateful: i.e. exceptions are re-thrown,but the retry policy is applied with the same policy to subsequent invocations with the same arguments. If false then retryable exceptions are not re-thrown.
maxAttempts the maximum number of attempts (including the first failure),defaults to 3
maxAttemptsExpression an expression evaluated to the maximum number of attempts (including the first failure),defaults to 3
backoff Specify the backoff properties for retrying this operation. The default is a simple specification with no properties.
exceptionExpression Specify an expression to be evaluated after the SimpleRetryPolicy.canRetry() returns true - can be used to conditionally suppress the retry.
listeners Bean names of retry listeners to use instead of default ones defined in Spring context.

上面是@Retryable的引數列表,引數較多,這裡就選擇幾個主要的來說明一下:

interceptor:可以通過該引數,指定方法攔截器的bean名稱

value:丟擲指定異常才會重試

include:和value一樣,預設為空,當exclude也為空時,預設所以異常

exclude:指定不處理的異常

maxAttempts:最大重試次數,預設3次

backoff:重試等待策略,預設使用@Backoff,@Backoff的value預設為1000L,我們設定為2000L;multiplier(指定延遲倍數)預設為0,表示固定暫停1秒後進行重試,如果把multiplier設定為1.5,則第一次重試為2秒,第二次為3秒,第三次為4.5秒。

我們把上面的例子執行一下,來看看效果:

2019-12-25 11:38:02.492 INFO 25664 --- [   main] c.f.l.service.impl.DoRetryServiceImpl : 開始通知下游系統
2019-12-25 11:38:02.493 INFO 25664 --- [   main] c.f.l.service.impl.DoRetryServiceImpl : 通知下游系統
2019-12-25 11:38:04.494 INFO 25664 --- [   main] c.f.l.service.impl.DoRetryServiceImpl : 開始通知下游系統
2019-12-25 11:38:04.495 INFO 25664 --- [   main] c.f.l.service.impl.DoRetryServiceImpl : 通知下游系統
2019-12-25 11:38:07.496 INFO 25664 --- [   main] c.f.l.service.impl.DoRetryServiceImpl : 開始通知下游系統
2019-12-25 11:38:07.496 INFO 25664 --- [   main] c.f.l.service.impl.DoRetryServiceImpl : 通知下游系統
2019-12-25 11:38:11.997 INFO 25664 --- [   main] c.f.l.service.impl.DoRetryServiceImpl : 開始通知下游系統
2019-12-25 11:38:11.997 INFO 25664 --- [   main] c.f.l.service.impl.DoRetryServiceImpl : 通知下游系統

java.lang.RuntimeException: 通知下游系統異常
...
...
...

可以看到,三次之後丟擲了RuntimeException的異常。

@Recover

當重試耗盡時,RetryOperations可以將控制傳遞給另一個回撥,即RecoveryCallback。Spring-Retry還提供了@Recover註解,用於@Retryable重試失敗後處理方法,此方法裡的異常一定要是@Retryable方法裡丟擲的異常,否則不會呼叫這個方法。

@Recover

public boolean doRecover(Throwable e,boolean isRetry) throws ArithmeticException {
 log.info("全部重試失敗,執行doRecover");
 return false;
}

對於@Recover註解的方法,需要特別注意的是:

1、方法的返回值必須與@Retryable方法一致

2、方法的第一個引數,必須是Throwable型別的,建議是與@Retryable配置的異常一致,其他的引數,需要與@Retryable方法的引數一致

/**
 * Annotation for a method invocation that is a recovery handler. A suitable recovery
 * handler has a first parameter of type Throwable (or a subtype of Throwable) and a
 * return value of the same type as the <code>@Retryable</code> method to recover from.
 * The Throwable first argument is optional (but a method without it will only be called
 * if no others match). Subsequent arguments are populated from the argument list of the
 * failed method in order.
 */

@Recover不生效的問題

在測試過程中,發現@Recover無法生效,執行時丟擲異常資訊:

org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method; nested exception is java.lang.ArithmeticException: / by zero

at org.springframework.retry.annotation.RecoverAnnotationRecoveryHandler.recover(RecoverAnnotationRecoveryHandler.java:61)
at org.springframework.retry.interceptor.RetryOperationsInterceptor$ItemRecovererCallback.recover(RetryOperationsInterceptor.java:141)
at org.springframework.retry.support.RetryTemplate.handleRetryExhausted(RetryTemplate.java:512)
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:351)
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:180)
at org.springframework.retry.interceptor.RetryOperationsInterceptor.invoke(RetryOperationsInterceptor.java:115)
at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:153)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy157.doRetry(Unknown Source)

追蹤一下異常的資訊,進入到RecoverAnnotationRecoveryHandler中,找到報錯的方法public T recover(Object[] args,Throwable cause),看一下其實現:

SpringBoot @Retryable註解方式

發現報錯處,是因為method為空而導致的,明明我已經在需要執行的方法上註解了@Recover,為什麼還會找不到方法呢?很奇怪,再來深入追蹤一下:

SpringBoot @Retryable註解方式

打斷點到這,發現methods列表是空的,那麼methods列表是什麼時候初始化的呢?繼續追蹤:

SpringBoot @Retryable註解方式

發現了初始化methods列表的地方,這裡會掃描註解了@Recover註解的方法,將其加入到methds列表中,那麼為什麼沒有掃描到我們註解了的方法呢?

SpringBoot @Retryable註解方式

很奇怪,為什麼明明註解了@Recover,這裡卻沒有掃描到呢?

我有點懷疑Spring掃描的部分,可能有什麼問題了,回頭去看看@EnableRetry是怎麼說的:

SpringBoot @Retryable註解方式

終於找到問題的所在了,對於@EnableRetry中的proxyTargetClass引數,是控制是否對使用介面實現的bean開啟代理類,預設的情況下,是不開啟的,問題原因就是這個,我們來實驗一下,把這個引數改成true:

@EnableRetry(proxyTargetClass = true)

再次執行,果然沒有問題了。

由此得出結論,當使用介面實現的bean時,需要將EnableRetry的引數改為true,非介面的實現,可以使用預設配置,即false。

結語

本篇主要簡單介紹了Springboot中的Retryable的使用,主要的適用場景為在呼叫第三方介面或者使用MQ時。由於會出現網路抖動,連線超時等網路異常。

以上這篇SpringBoot @Retryable註解方式就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。