好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Spring Boot中使用Spring Retry重试框架的操作方法

Spring Retry 在SpringBoot 中的应用

Spring Retry提供了自动重新调用失败的操作的功能。这在错误可能是暂时的(例如瞬时网络故障)的情况下很有用。 从2.2.0版本开始,重试功能已从Spring Batch中撤出,成为一个独立的新库:Spring Retry

Maven依赖

?

1

2

3

4

5

6

7

8

9

<dependency>

     <groupId>org.springframework.retry</groupId>

     <artifactId>spring-retry</artifactId>

</dependency>

<!-- also need to add Spring AOP into our project-->

<dependency>

     <groupId>org.springframework</groupId>

     <artifactId>spring-aspects</artifactId>

</dependency>

注解使用

开启Retry功能

在启动类中使用@EnableRetry注解

?

1

2

3

4

5

6

7

8

9

10

11

12

package org.example;

 

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.retry.annotation.EnableRetry;

@SpringBootApplication

@EnableRetry

public class RetryApp {

     public static void main(String[] args) {

         SpringApplication.run(RetryApp. class , args);

     }

}

注解@Retryable

需要在重试的代码中加入重试注解 @Retryable

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

package org.example;

import lombok.extern.slf4j.Slf4j;

import org.springframework.retry.annotation.Backoff;

import org.springframework.retry.annotation.Recover;

import org.springframework.retry.annotation.Retryable;

import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Service

@Slf4j

public class RetryService {

     @Retryable (value = IllegalAccessException. class )

     public void service1() throws IllegalAccessException {

         log.info( "do something... {}" , LocalDateTime.now());

         throw new IllegalAccessException( "manual exception" );

     }

}

默认情况下,会重试3次,间隔1秒

我们可以从注解 @Retryable 中看到

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

@Target ({ ElementType.METHOD, ElementType.TYPE })

@Retention (RetentionPolicy.RUNTIME)

@Documented

public @interface Retryable {

 

     /**

      * Retry interceptor bean name to be applied for retryable method. Is mutually

      * exclusive with other attributes.

      * @return the retry interceptor bean name

      */

     String interceptor() default "" ;

      * Exception types that are retryable. Synonym for includes(). Defaults to empty (and

      * if excludes is also empty all exceptions are retried).

      * @return exception types to retry

     Class<? extends Throwable>[] value() default {};

      * Exception types that are retryable. Defaults to empty (and if excludes is also

      * empty all exceptions are retried).

     Class<? extends Throwable>[] include() default {};

      * Exception types that are not retryable. Defaults to empty (and if includes is also

      * If includes is empty but excludes is not, all not excluded exceptions are retried

      * @return exception types not to retry

     Class<? extends Throwable>[] exclude() default {};

      * A unique label for statistics reporting. If not provided the caller may choose to

      * ignore it, or provide a default .

      *

      * @return the label for the statistics

     String label() default "" ;

      * 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.

      * @return true if retry is stateful, default false

     boolean stateful() default false ;

      * @return the maximum number of attempts (including the first failure), defaults to 3

     int maxAttempts() default 3 ;  //默认重试次数3次

      * @return an expression evaluated to the maximum number of attempts (including the first failure), defaults to 3

      * Overrides { @link #maxAttempts()}.

      * @since 1.2

     String maxAttemptsExpression() default "" ;

      * Specify the backoff properties for retrying this operation. The default is a

      * simple { @link Backoff} specification with no properties - see it's documentation

      * for defaults.

      * @return a backoff specification

     Backoff backoff() default @Backoff (); //默认的重试中的退避策略

      * Specify an expression to be evaluated after the { @code SimpleRetryPolicy.canRetry()}

      * returns true - can be used to conditionally suppress the retry. Only invoked after

      * an exception is thrown. The root object for the evaluation is the last { @code Throwable}.

      * Other beans in the context can be referenced.

      * For example:

      * <pre class =code>

      *  { @code "message.contains('you can retry this')" }.

      * </pre>

      * and

      *  { @code "@someBean.shouldRetry(#root)" }.

      * @return the expression.

     String exceptionExpression() default "" ;

      * Bean names of retry listeners to use instead of default ones defined in Spring context

      * @return retry listeners bean names

     String[] listeners() default {};

}

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

@Target (ElementType.TYPE)

@Retention (RetentionPolicy.RUNTIME)

@Documented

public @interface Backoff {

 

     /**

      * Synonym for {@link #delay()}.

      *

      * @return the delay in milliseconds (default 1000)

      */

     long value() default 1000 ; //默认的重试间隔1秒

      * A canonical backoff period. Used as an initial value in the exponential case , and

      * as a minimum value in the uniform case .

      * @return the initial or canonical backoff period in milliseconds ( default 1000 )

     long delay() default 0 ;

      * The maximimum wait (in milliseconds) between retries. If less than the

      * { @link #delay()} then the default of

      * { @value org.springframework.retry.backoff.ExponentialBackOffPolicy#DEFAULT_MAX_INTERVAL}

      * is applied.

      * @return the maximum delay between retries ( default 0 = ignored)

     long maxDelay() default 0 ;

      * If positive, then used as a multiplier for generating the next delay for backoff.

      * @return a multiplier to use to calculate the next backoff delay ( default 0 =

      * ignored)

     double multiplier() default 0 ;

      * An expression evaluating to the canonical backoff period. Used as an initial value

      * in the exponential case , and as a minimum value in the uniform case . Overrides

      * { @link #delay()}.

      * @return the initial or canonical backoff period in milliseconds.

      * @since 1.2

     String delayExpression() default "" ;

      * An expression evaluating to the maximimum wait (in milliseconds) between retries.

      * If less than the { @link #delay()} then the default of

      * is applied. Overrides { @link #maxDelay()}

     String maxDelayExpression() default "" ;

      * Evaluates to a vaule used as a multiplier for generating the next delay for

      * backoff. Overrides { @link #multiplier()}.

      * @return a multiplier expression to use to calculate the next backoff delay ( default

      * 0 = ignored)

     String multiplierExpression() default "" ;

      * In the exponential case ({ @link #multiplier()} &gt; 0 ) set this to true to have the

      * backoff delays randomized, so that the maximum delay is multiplier times the

      * previous delay and the distribution is uniform between the two values.

      * @return the flag to signal randomization is required ( default false )

     boolean random() default false ;

}

我们来运行测试代码

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

package org.example;

 

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest

class RetryServiceTest {

     @Autowired

     private RetryService retryService;

     @Test

     void testService1() throws IllegalAccessException {

         retryService.service1();

     }

}

运行结果如下:

2021-01-05 19:40:41.221  INFO 3548 --- [           main] org.example.RetryService                 : do something... 2021-01-05T19:40:41.221763300
2021-01-05 19:40:42.224  INFO 3548 --- [           main] org.example.RetryService                 : do something... 2021-01-05T19:40:42.224436500
2021-01-05 19:40:43.225  INFO 3548 --- [           main] org.example.RetryService                 : do something... 2021-01-05T19:40:43.225189300

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

java.lang.IllegalAccessException: manual exception

     at org.example.RetryService.service1(RetryService.java: 19 )

     at org.example.RetryService$$FastClassBySpringCGLIB$$c0995ddb.invoke(<generated>)

     at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java: 218 )

     at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java: 769 )

     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java: 163 )

     at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java: 747 )

     at org.springframework.retry.interceptor.RetryOperationsInterceptor$ 1 .doWithRetry(RetryOperationsInterceptor.java: 91 )

     at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java: 287 )

     at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java: 164 )

     at org.springframework.retry.interceptor.RetryOperationsInterceptor.invoke(RetryOperationsInterceptor.java: 118 )

     at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java: 153 )

     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java: 186 )

     at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java: 689 )

     at org.example.RetryService$$EnhancerBySpringCGLIB$$499afa1d.service1(<generated>)

     at org.example.RetryServiceTest.testService1(RetryServiceTest.java: 16 )

     at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

     at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62 )

     at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43 )

     at java.base/java.lang.reflect.Method.invoke(Method.java: 566 )

     at org.junit.platform测试数据mons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java: 675 )

     at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java: 60 )

     at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java: 125 )

     at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java: 132 )

     at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java: 124 )

     at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java: 74 )

     at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$ 0 (ExecutableInvoker.java: 115 )

     at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$ 0 (ExecutableInvoker.java: 105 )

     at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java: 104 )

     at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java: 62 )

     at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java: 43 )

     at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java: 35 )

     at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java: 104 )

     at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java: 98 )

     at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$ 6 (TestMethodTestDescriptor.java: 202 )

     at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java: 73 )

     at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java: 198 )

     at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java: 135 )

     at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java: 69 )

     at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$ 5 (NodeTestTask.java: 135 )

     at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$ 7 (NodeTestTask.java: 125 )

     at org.junit.platform.engine.support.hierarchical.Node.around(Node.java: 135 )

     at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$ 8 (NodeTestTask.java: 123 )

     at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java: 122 )

     at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java: 80 )

     at java.base/java.util.ArrayList.forEach(ArrayList.java: 1540 )

     at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java: 38 )

     at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$ 5 (NodeTestTask.java: 139 )

     at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java: 32 )

     at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java: 57 )

     at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java: 51 )

     at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java: 229 )

     at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$ 6 (DefaultLauncher.java: 197 )

     at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java: 211 )

     at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java: 191 )

     at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java: 128 )

     at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java: 71 )

     at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java: 33 )

     at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java: 220 )

     at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java: 53 )

可以看到重新执行了3次 service1() 方法,然后间隔是1秒,然后最后还是重试失败,所以抛出了异常

既然我们看到了注解 @Retryable 中有这么多参数可以设置,那我们就来介绍几个常用的配置。

?

1

2

3

4

5

@Retryable (include = IllegalAccessException. class , maxAttempts = 5 )

public void service2() throws IllegalAccessException {

     log.info( "do something... {}" , LocalDateTime.now());

     throw new IllegalAccessException( "manual exception" );

}

首先是 maxAttempts ,用于设置重试次数

2021-01-06 09:30:11.263  INFO 15612 --- [           main] org.example.RetryService                 : do something... 2021-01-06T09:30:11.263621900
2021-01-06 09:30:12.265  INFO 15612 --- [           main] org.example.RetryService                 : do something... 2021-01-06T09:30:12.265629100
2021-01-06 09:30:13.265  INFO 15612 --- [           main] org.example.RetryService                 : do something... 2021-01-06T09:30:13.265701
2021-01-06 09:30:14.266  INFO 15612 --- [           main] org.example.RetryService                 : do something... 2021-01-06T09:30:14.266705400
2021-01-06 09:30:15.266  INFO 15612 --- [           main] org.example.RetryService                 : do something... 2021-01-06T09:30:15.266733200

java.lang.IllegalAccessException: manual exception
....

从运行结果可以看到,方法执行了5次。

下面来介绍 maxAttemptsExpression 的设置

?

1

2

3

4

5

@Retryable (value = IllegalAccessException. class , maxAttemptsExpression = "${maxAttempts}" )

public void service3() throws IllegalAccessException {

     log.info( "do something... {}" , LocalDateTime.now());

     throw new IllegalAccessException( "manual exception" );

}

maxAttemptsExpression 则可以使用表达式,比如上述就是通过获取配置中maxAttempts的值,我们可以在application.yml设置。上述其实省略掉了SpEL表达式 #{....} ,运行结果的话可以发现方法执行了4次..

?

1

maxAttempts: 4

我们可以使用SpEL表达式

?

1

2

3

4

5

6

7

8

9

10

11

@Retryable (value = IllegalAccessException. class , maxAttemptsExpression = "#{1+1}" )

public void service3_1() throws IllegalAccessException {

     log.info( "do something... {}" , LocalDateTime.now());

     throw new IllegalAccessException( "manual exception" );

}

 

@Retryable (value = IllegalAccessException. class , maxAttemptsExpression = "#{${maxAttempts}}" ) //效果和上面的一样

public void service3_2() throws IllegalAccessException {

     log.info( "do something... {}" , LocalDateTime.now());

     throw new IllegalAccessException( "manual exception" );

}

接着我们下面来看看 exceptionExpression , 一样也是写SpEL表达式

?

1

2

3

4

5

6

7

8

9

10

@Retryable (value = IllegalAccessException. class , exceptionExpression = "message.contains('test')" )

public void service4(String exceptionMessage) throws IllegalAccessException {

     log.info( "do something... {}" , LocalDateTime.now());

     throw new IllegalAccessException(exceptionMessage);

}

     @Retryable (value = IllegalAccessException. class , exceptionExpression = "#{message.contains('test')}" )

public void service4_3(String exceptionMessage) throws IllegalAccessException {

     log.info( "do something... {}" , LocalDateTime.now());

     throw new IllegalAccessException(exceptionMessage);

}

上面的表达式 exceptionExpression = "message.contains('test')" 的作用其实是获取到抛出来exception的message(调用了 getMessage() 方法),然后判断message的内容里面是否包含了 test 字符串,如果包含的话就会执行重试。所以如果调用方法的时候传入的参数 exceptionMessage 中包含了 test 字符串的话就会执行重试。

但这里值得注意的是, Spring Retry 1.2.5之后 exceptionExpression 是可以省略掉 #{...}

Since Spring Retry 1.2.5, for exceptionExpression , templated expressions ( #{...} ) are deprecated in favor of simple expression strings ( message.contains('this can be retried') ).

使用1.2.5之后的版本运行是没有问题的

?

1

2

3

4

5

<dependency>

     <groupId>org.springframework.retry</groupId>

     <artifactId>spring-retry</artifactId>

     <version> 1.3 . 0 </version>

</dependency>

但是如果使用1.2.5版本之前包括1.2.5版本的话,运行的时候会报错如下:

2021-01-06 09:52:45.209  INFO 23220 --- [           main] org.example.RetryService                 : do something... 2021-01-06T09:52:45.209178200

org.springframework.expression.spel.SpelEvaluationException: EL1001E: Type conversion problem, cannot convert from java.lang.String to java.lang.Boolean
    at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:75)
    at org.springframework.expression测试数据mon.ExpressionUtils.convertTypedValue(ExpressionUtils.java:57)
    at org.springframework.expression测试数据mon.LiteralExpression.getValue(LiteralExpression.java:106)
    at org.springframework.retry.policy.ExpressionRetryPolicy.canRetry(ExpressionRetryPolicy.java:113)
    at org.springframework.retry.support.RetryTemplate.canRetry(RetryTemplate.java:375)
    at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:304)
    at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:164)
    at org.springframework.retry.interceptor.RetryOperationsInterceptor.invoke(RetryOperationsInterceptor.java:118)
    at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:153)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691)
    at org.example.RetryService$$EnhancerBySpringCGLIB$$d321a75e.service4(<generated>)
    at org.example.RetryServiceTest.testService4_2(RetryServiceTest.java:46)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.platform测试数据mons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:212)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:208)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:137)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:71)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Boolean] for value 'message.contains('test')'; nested exception is java.lang.IllegalArgumentException: Invalid boolean value 'message.contains('test')'
    at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:191)
    at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:70)
    ... 76 more
Caused by: java.lang.IllegalArgumentException: Invalid boolean value 'message.contains('test')'
    at org.springframework.core.convert.support.StringToBooleanConverter.convert(StringToBooleanConverter.java:63)
    at org.springframework.core.convert.support.StringToBooleanConverter.convert(StringToBooleanConverter.java:31)
    at org.springframework.core.convert.support.GenericConversionService$ConverterAdapter.convert(GenericConversionService.java:385)
    at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41)
    ... 78 more

还可以在表达式中执行一个方法,前提是方法的类在spring容器中注册了, @retryService 其实就是获取bean name为 retryService 的bean,然后调用里面的 checkException 方法,传入的参数为 #root ,它其实就是抛出来的exception对象。一样的也是可以省略 #{...}

?

1

2

3

4

5

6

7

8

9

10

11

@Retryable (value = IllegalAccessException. class , exceptionExpression = "#{@retryService.checkException(#root)}" )

    public void service5(String exceptionMessage) throws IllegalAccessException {

        log.info( "do something... {}" , LocalDateTime.now());

        throw new IllegalAccessException(exceptionMessage);

    }

    @Retryable (value = IllegalAccessException. class , exceptionExpression = "@retryService.checkException(#root)" )

    public void service5_1(String exceptionMessage) throws IllegalAccessException {

   

    public boolean checkException(Exception e) {

        log.error( "error message:{}" , e.getMessage());

        return true ; //返回true的话表明会执行重试,如果返回false则不会执行重试

运行结果:

2021-01-06 13:33:52.913  INFO 23052 --- [           main] org.example.RetryService                 : do something... 2021-01-06T13:33:52.913404
2021-01-06 13:33:52.981 ERROR 23052 --- [           main] org.example.RetryService                 : error message:test message
2021-01-06 13:33:53.990 ERROR 23052 --- [           main] org.example.RetryService                 : error message:test message
2021-01-06 13:33:53.990  INFO 23052 --- [           main] org.example.RetryService                 : do something... 2021-01-06T13:33:53.990947400
2021-01-06 13:33:53.990 ERROR 23052 --- [           main] org.example.RetryService                 : error message:test message
2021-01-06 13:33:54.992 ERROR 23052 --- [           main] org.example.RetryService                 : error message:test message
2021-01-06 13:33:54.992  INFO 23052 --- [           main] org.example.RetryService                 : do something... 2021-01-06T13:33:54.992342900

当然还有更多表达式的用法了...

?

1

2

3

4

5

6

7

8

9

10

@Retryable (exceptionExpression = "#{#root instanceof T(java.lang.IllegalAccessException)}" ) //判断exception的类型

     public void service5_2(String exceptionMessage) {

         log.info( "do something... {}" , LocalDateTime.now());

         throw new NullPointerException(exceptionMessage);

     }

     @Retryable (exceptionExpression = "#root instanceof T(java.lang.IllegalAccessException)" )

     public void service5_3(String exceptionMessage) {

         log.info( "do something... {}" , LocalDateTime.now());

         throw new NullPointerException(exceptionMessage);

     }

?

1

2

3

4

5

6

7

8

9

10

@Retryable (exceptionExpression = "myMessage.contains('test')" ) //查看自定义的MyException中的myMessage的值是否包含test字符串

    public void service5_4(String exceptionMessage) throws MyException {

        log.info( "do something... {}" , LocalDateTime.now());

        throw new MyException(exceptionMessage); //自定义的exception

    }

    @Retryable (exceptionExpression = "#root.myMessage.contains('test')" )  //和上面service5_4方法的效果一样

    public void service5_5(String exceptionMessage) throws MyException {

        log.info( "do something... {}" , LocalDateTime.now());

        throw new MyException(exceptionMessage);

    }

?

1

2

3

4

5

6

7

8

9

10

11

package org.example;

import lombok.Getter;

import lombok.Setter;

@Getter

@Setter

public class MyException extends Exception {

     private String myMessage;

     public MyException(String myMessage) {

         this .myMessage = myMessage;

     }

}

下面再来看看另一个配置 exclude

?

1

2

3

4

5

@Retryable (exclude = MyException. class )

    public void service6(String exceptionMessage) throws MyException {

        log.info( "do something... {}" , LocalDateTime.now());

        throw new MyException(exceptionMessage);

    }

这个 exclude 属性可以帮我们排除一些我们不想重试的异常

最后我们来看看这个 backoff 重试等待策略, 默认使用 @Backoff 注解。

我们先来看看这个 @Backoff 的 value 属性,用于设置重试间隔

?

1

2

3

4

5

6

@Retryable (value = IllegalAccessException. class ,

             backoff = @Backoff (value = 2000 ))

     public void service7() throws IllegalAccessException {

         log.info( "do something... {}" , LocalDateTime.now());

         throw new IllegalAccessException();

     }

运行结果可以看出来重试的间隔为2秒

2021-01-06 14:47:38.036  INFO 21116 --- [           main] org.example.RetryService                 : do something... 2021-01-06T14:47:38.036732600
2021-01-06 14:47:40.038  INFO 21116 --- [           main] org.example.RetryService                 : do something... 2021-01-06T14:47:40.037753600
2021-01-06 14:47:42.046  INFO 21116 --- [           main] org.example.RetryService                 : do something... 2021-01-06T14:47:42.046642900
java.lang.IllegalAccessException
    at org.example.RetryService.service7(RetryService.java:113)
...

接下来介绍 @Backoff 的 delay 属性,它与 value 属性不能共存,当 delay 不设置的时候会去读 value 属性设置的值,如果 delay 设置的话则会忽略 value 属性

?

1

2

3

4

5

6

@Retryable (value = IllegalAccessException. class ,

             backoff = @Backoff (value = 2000 ,delay = 500 ))

     public void service8() throws IllegalAccessException {

         log.info( "do something... {}" , LocalDateTime.now());

         throw new IllegalAccessException();

     }

运行结果可以看出,重试的时间间隔为500ms

2021-01-06 15:22:42.271  INFO 13512 --- [           main] org.example.RetryService                 : do something... 2021-01-06T15:22:42.271504800
2021-01-06 15:22:42.772  INFO 13512 --- [           main] org.example.RetryService                 : do something... 2021-01-06T15:22:42.772234900
2021-01-06 15:22:43.273  INFO 13512 --- [           main] org.example.RetryService                 : do something... 2021-01-06T15:22:43.273246700
java.lang.IllegalAccessException
    at org.example.RetryService.service8(RetryService.java:121)

接下来我们来看``@Backoff 的 multiplier`的属性, 指定延迟倍数, 默认为0。

?

1

2

3

4

5

6

@Retryable (value = IllegalAccessException. class ,maxAttempts = 4 ,

             backoff = @Backoff (delay = 2000 , multiplier = 2 ))

     public void service9() throws IllegalAccessException {

         log.info( "do something... {}" , LocalDateTime.now());

         throw new IllegalAccessException();

     }

multiplier 设置为2,则表示第一次重试间隔为2s,第二次为4秒,第三次为8s

运行结果如下:

2021-01-06 15:58:07.458  INFO 23640 --- [           main] org.example.RetryService                 : do something... 2021-01-06T15:58:07.458245500
2021-01-06 15:58:09.478  INFO 23640 --- [           main] org.example.RetryService                 : do something... 2021-01-06T15:58:09.478681300
2021-01-06 15:58:13.478  INFO 23640 --- [           main] org.example.RetryService                 : do something... 2021-01-06T15:58:13.478921900
2021-01-06 15:58:21.489  INFO 23640 --- [           main] org.example.RetryService                 : do something... 2021-01-06T15:58:21.489240600
java.lang.IllegalAccessException
    at org.example.RetryService.service9(RetryService.java:128)
...

接下来我们来看看这个 @Backoff 的 maxDelay 属性,设置最大的重试间隔,当超过这个最大的重试间隔的时候,重试的间隔就等于 maxDelay 的值

?

1

2

3

4

5

6

@Retryable (value = IllegalAccessException. class ,maxAttempts = 4 ,

             backoff = @Backoff (delay = 2000 , multiplier = 2 ,maxDelay = 5000 ))

     public void service10() throws IllegalAccessException {

         log.info( "do something... {}" , LocalDateTime.now());

         throw new IllegalAccessException();

     }

运行结果:

2021-01-06 16:12:37.377  INFO 5024 --- [           main] org.example.RetryService                 : do something... 2021-01-06T16:12:37.377616100
2021-01-06 16:12:39.381  INFO 5024 --- [           main] org.example.RetryService                 : do something... 2021-01-06T16:12:39.381299400
2021-01-06 16:12:43.382  INFO 5024 --- [           main] org.example.RetryService                 : do something... 2021-01-06T16:12:43.382169500
2021-01-06 16:12:48.396  INFO 5024 --- [           main] org.example.RetryService                 : do something... 2021-01-06T16:12:48.396327600
java.lang.IllegalAccessException
    at org.example.RetryService.service10(RetryService.java:135)

可以最后的最大重试间隔是5秒

注解@Recover

当 @Retryable 方法重试失败之后,最后就会调用 @Recover 方法。用于 @Retryable 失败时的[兜底]处理方法。 @Recover 的方法必须要与 @Retryable 注解的方法保持一致,第一入参为要重试的异常,其他参数与 @Retryable 保持一致,返回值也要一样,否则无法执行!

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@Retryable (value = IllegalAccessException. class )

     public void service11() throws IllegalAccessException {

         log.info( "do something... {}" , LocalDateTime.now());

         throw new IllegalAccessException();

     }

 

     @Recover

     public void recover11(IllegalAccessException e) {

         log.info( "service retry after Recover => {}" , e.getMessage());

     //=========================

     @Retryable (value = ArithmeticException. class )

     public int service12() throws IllegalAccessException {

         return 1 / 0 ;

     public int recover12(ArithmeticException e) {

         return 0 ;

     public int service13(String message) throws IllegalAccessException {

         log.info( "do something... {},{}" , message, LocalDateTime.now());

     public int recover13(ArithmeticException e, String message) {

         log.info( "{},service retry after Recover => {}" , message, e.getMessage());

注解@CircuitBreaker

熔断模式:指在具体的重试机制下失败后打开断路器,过了一段时间,断路器进入半开状态,允许一个进入重试,若失败再次进入断路器,成功则关闭断路器,注解为 @CircuitBreaker ,具体包括熔断打开时间、重置过期时间

?

1

2

3

4

5

6

7

8

9

10

11

12

// openTimeout时间范围内失败maxAttempts次数后,熔断打开resetTimeout时长

     @CircuitBreaker (openTimeout = 1000 , resetTimeout = 3000 , value = NullPointerException. class )

     public void circuitBreaker( int num) {

         log.info( " 进入断路器方法num={}" , num);

         if (num > 8 ) return ;

         Integer n = null ;

         System.err.println( 1 / n);

     }

     @Recover

     public void recover(NullPointerException e) {

         log.info( "service retry after Recover => {}" , e.getMessage());

     }

测试方法

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

@Test

     public void testCircuitBreaker() throws InterruptedException {

         System.err.println( "尝试进入断路器方法,并触发异常..." );

         retryService.circuitBreaker( 1 );

         retryService.circuitBreaker( 1 );

         retryService.circuitBreaker( 9 );

         retryService.circuitBreaker( 9 );

         System.err.println( "在openTimeout 1秒之内重试次数为2次,未达到触发熔断, 断路器依然闭合..." );

         TimeUnit.SECONDS.sleep( 1 );

         System.err.println( "超过openTimeout 1秒之后, 因为未触发熔断,所以重试次数重置,可以正常访问...,继续重试3次方法..." );

         retryService.circuitBreaker( 1 );

         retryService.circuitBreaker( 1 );

         retryService.circuitBreaker( 1 );

         System.err.println( "在openTimeout 1秒之内重试次数为3次,达到触发熔断,不会执行重试,只会执行恢复方法..." );

         retryService.circuitBreaker( 1 );

         TimeUnit.SECONDS.sleep( 2 );

         retryService.circuitBreaker( 9 );

         TimeUnit.SECONDS.sleep( 3 );

         System.err.println( "超过resetTimeout 3秒之后,断路器重新闭合...,可以正常访问" );

         retryService.circuitBreaker( 9 );

         retryService.circuitBreaker( 9 );

         retryService.circuitBreaker( 9 );

         retryService.circuitBreaker( 9 );

         retryService.circuitBreaker( 9 );

     }

运行结果:

尝试进入断路器方法,并触发异常...
2021-01-07 21:44:20.842  INFO 7464 --- [           main] org.example.RetryService                 :  进入断路器方法num=1
2021-01-07 21:44:20.844  INFO 7464 --- [           main] org.example.RetryService                 : service retry after Recover => null
2021-01-07 21:44:20.845  INFO 7464 --- [           main] org.example.RetryService                 :  进入断路器方法num=1
2021-01-07 21:44:20.845  INFO 7464 --- [           main] org.example.RetryService                 : service retry after Recover => null
2021-01-07 21:44:20.845  INFO 7464 --- [           main] org.example.RetryService                 :  进入断路器方法num=9
2021-01-07 21:44:20.845  INFO 7464 --- [           main] org.example.RetryService                 :  进入断路器方法num=9
在openTimeout 1秒之内重试次数为2次,未达到触发熔断, 断路器依然闭合...
超过openTimeout 1秒之后, 因为未触发熔断,所以重试次数重置,可以正常访问...,继续重试3次方法...
2021-01-07 21:44:21.846  INFO 7464 --- [           main] org.example.RetryService                 :  进入断路器方法num=1
2021-01-07 21:44:21.847  INFO 7464 --- [           main] org.example.RetryService                 : service retry after Recover => null
2021-01-07 21:44:21.847  INFO 7464 --- [           main] org.example.RetryService                 :  进入断路器方法num=1
2021-01-07 21:44:21.847  INFO 7464 --- [           main] org.example.RetryService                 : service retry after Recover => null
2021-01-07 21:44:21.847  INFO 7464 --- [           main] org.example.RetryService                 :  进入断路器方法num=1
2021-01-07 21:44:21.848  INFO 7464 --- [           main] org.example.RetryService                 : service retry after Recover => null
在openTimeout 1秒之内重试次数为3次,达到触发熔断,不会执行重试,只会执行恢复方法...
2021-01-07 21:44:21.848  INFO 7464 --- [           main] org.example.RetryService                 : service retry after Recover => null
2021-01-07 21:44:23.853  INFO 7464 --- [           main] org.example.RetryService                 : service retry after Recover => null
超过resetTimeout 3秒之后,断路器重新闭合...,可以正常访问
2021-01-07 21:44:26.853  INFO 7464 --- [           main] org.example.RetryService                 :  进入断路器方法num=9
2021-01-07 21:44:26.854  INFO 7464 --- [           main] org.example.RetryService                 :  进入断路器方法num=9
2021-01-07 21:44:26.855  INFO 7464 --- [           main] org.example.RetryService                 :  进入断路器方法num=9
2021-01-07 21:44:26.855  INFO 7464 --- [           main] org.example.RetryService                 :  进入断路器方法num=9
2021-01-07 21:44:26.856  INFO 7464 --- [           main] org.example.RetryService                 :  进入断路器方法num=9

RetryTemplate

RetryTemplate配置

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

package org.example;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.retry.backoff.FixedBackOffPolicy;

import org.springframework.retry.policy.SimpleRetryPolicy;

import org.springframework.retry.support.RetryTemplate;

@Configuration

public class AppConfig {

   

     @Bean

     public RetryTemplate retryTemplate() {

         RetryTemplate retryTemplate = new RetryTemplate();

         SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); //设置重试策略

         retryPolicy.setMaxAttempts( 2 );

         retryTemplate.setRetryPolicy(retryPolicy);

         FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy(); //设置退避策略

         fixedBackOffPolicy.setBackOffPeriod(2000L);

         retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

        

         return retryTemplate;

     }

}

可以看到这些配置跟我们直接写注解的方式是差不多的,这里就不过多的介绍了。。

使用RetryTemplate

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

package org.example;

 

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.retry.RetryCallback;

import org.springframework.retry.RetryContext;

import org.springframework.retry.support.RetryTemplate;

@SpringBootTest

public class RetryTemplateTest {

     @Autowired

     private RetryTemplate retryTemplate;

     private RetryTemplateService retryTemplateService;

     @Test

     void test1() throws IllegalAccessException {

         retryTemplate.execute( new RetryCallback<Object, IllegalAccessException>() {

             @Override

             public Object doWithRetry(RetryContext context) throws IllegalAccessException {

                  retryTemplateService.service1();

                 return null ;

             }

         });

     }

     void test2() throws IllegalAccessException {

                 retryTemplateService.service1();

         }, new RecoveryCallback<Object>() {

             public Object recover(RetryContext context) throws Exception {

                 log.info( "RecoveryCallback...." );

}

RetryOperations 定义重试的API, RetryTemplate 是API的模板模式实现,实现了重试和熔断。提供的API如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

package org.springframework.retry;

 

import org.springframework.retry.support.DefaultRetryState;

/**

  * Defines the basic set of operations implemented by {@link RetryOperations} to execute

  * operations with configurable retry behaviour.

  *

  * @author Rob Harrop

  * @author Dave Syer

  */

public interface RetryOperations {

     /**

      * Execute the supplied {@link RetryCallback} with the configured retry semantics. See

      * implementations for configuration details.

      * @param <T> the return value

      * @param retryCallback the {@link RetryCallback}

      * @param <E> the exception to throw

      * @return the value returned by the {@link RetryCallback} upon successful invocation.

      * @throws E any {@link Exception} raised by the {@link RetryCallback} upon

      * unsuccessful retry.

      * @throws E the exception thrown

      */

     <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback) throws E;

      * Execute the supplied { @link RetryCallback} with a fallback on exhausted retry to

      * the { @link RecoveryCallback}. See implementations for configuration details.

      * @param recoveryCallback the { @link RecoveryCallback}

      * @param retryCallback the { @link RetryCallback} { @link RecoveryCallback} upon

      * @param <T> the type to return

      * @param <E> the type of the exception

      * @return the value returned by the { @link RetryCallback} upon successful invocation,

      * and that returned by the { @link RecoveryCallback} otherwise.

      * @throws E any { @link Exception} raised by the unsuccessful retry.

     <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback)

             throws E;

      * A simple stateful retry. Execute the supplied { @link RetryCallback} with a target

      * object for the attempt identified by the { @link DefaultRetryState}. Exceptions

      * thrown by the callback are always propagated immediately so the state is required

      * to be able to identify the previous attempt, if there is one - hence the state is

      * required. Normal patterns would see this method being used inside a transaction,

      * where the callback might invalidate the transaction if it fails.

      *

      * See implementations for configuration details.

      * @param retryState the { @link RetryState}

      * @param <T> the type of the return value

      * @param <E> the type of the exception to return

      * @throws E any { @link Exception} raised by the { @link RecoveryCallback}.

      * @throws ExhaustedRetryException if the last attempt for this state has already been

      * reached

     <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RetryState retryState)

             throws E, ExhaustedRetryException;

      * A stateful retry with a recovery path. Execute the supplied { @link RetryCallback}

      * with a fallback on exhausted retry to the { @link RecoveryCallback} and a target

      * object for the retry attempt identified by the { @link DefaultRetryState}.

      * @param <T> the return value type

      * @param <E> the exception type

      * @see #execute(RetryCallback, RetryState)

      * @throws E any { @link Exception} raised by the { @link RecoveryCallback} upon

     <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback,

             RetryState retryState) throws E;

}

下面主要介绍一下RetryTemplate配置的时候,需要设置的重试策略和退避策略

RetryPolicy

RetryPolicy是一个接口, 然后有很多具体的实现,我们先来看看它的接口中定义了什么方法

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

package org.springframework.retry;

 

import java.io.Serializable;

/**

  * A {@link RetryPolicy} is responsible for allocating and managing resources needed by

  * {@link RetryOperations}. The {@link RetryPolicy} allows retry operations to be aware of

  * their context. Context can be internal to the retry framework, e.g. to support nested

  * retries. Context can also be external, and the {@link RetryPolicy} provides a uniform

  * API for a range of different platforms for the external context.

  *

  * @author Dave Syer

  */

public interface RetryPolicy extends Serializable {

     /**

      * @param context the current retry status

      * @return true if the operation can proceed

      */

     boolean canRetry(RetryContext context);

      * Acquire resources needed for the retry operation. The callback is passed in so that

      * marker interfaces can be used and a manager can collaborate with the callback to

      * set up some state in the status token.

      * @param parent the parent context if we are in a nested retry.

      * @return a { @link RetryContext} object specific to this policy.

      *

     RetryContext open(RetryContext parent);

      * @param context a retry status created by the { @link #open(RetryContext)} method of

      * this policy.

     void close(RetryContext context);

      * Called once per retry attempt, after the callback fails.

      * @param context the current status object.

      * @param throwable the exception to throw

     void registerThrowable(RetryContext context, Throwable throwable);

}

我们来看看他有什么具体的实现类

SimpleRetryPolicy 默认最多重试3次 TimeoutRetryPolicy 默认在1秒内失败都会重试 ExpressionRetryPolicy 符合表达式就会重试 CircuitBreakerRetryPolicy 增加了熔断的机制,如果不在熔断状态,则允许重试 CompositeRetryPolicy 可以组合多个重试策略 NeverRetryPolicy 从不重试(也是一种重试策略哈) AlwaysRetryPolicy 总是重试 等等...

BackOffPolicy

看一下退避策略,退避是指怎么去做下一次的重试,在这里其实就是等待多长时间。

FixedBackOffPolicy 默认固定延迟1秒后执行下一次重试 ExponentialBackOffPolicy 指数递增延迟执行重试,默认初始0.1秒,系数是2,那么下次延迟0.2秒,再下次就是延迟0.4秒,如此类推,最大30秒。 ExponentialRandomBackOffPolicy 在上面那个策略上增加随机性 UniformRandomBackOffPolicy 这个跟上面的区别就是,上面的延迟会不停递增,这个只会在固定的区间随机 StatelessBackOffPolicy 这个说明是无状态的,所谓无状态就是对上次的退避无感知,从它下面的子类也能看出来 等等...

RetryListener

listener可以监听重试,并执行对应的回调方法

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

package org.springframework.retry;

 

/**

  * Interface for listener that can be used to add behaviour to a retry. Implementations of

  * {@link RetryOperations} can chose to issue callbacks to an interceptor during the retry

  * lifecycle.

  *

  * @author Dave Syer

  */

public interface RetryListener {

     /**

      * Called before the first attempt in a retry. For instance, implementers can set up

      * state that is needed by the policies in the {@link RetryOperations}. The whole

      * retry can be vetoed by returning false from this method, in which case a

      * {@link TerminatedRetryException} will be thrown.

      * @param <T> the type of object returned by the callback

      * @param <E> the type of exception it declares may be thrown

      * @param context the current {@link RetryContext}.

      * @param callback the current {@link RetryCallback}.

      * @return true if the retry should proceed.

      */

     <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback);

      * Called after the final attempt (successful or not). Allow the interceptor to clean

      * up any resource it is holding before control returns to the retry caller.

      * @param throwable the last exception that was thrown by the callback.

      * @param <E> the exception type

      * @param <T> the return value

     <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);

      * Called after every unsuccessful attempt at a retry.

      * @param <E> the exception to throw

     <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);

}

使用如下:

自定义一个Listener

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

package org.example;

 

import lombok.extern.slf4j.Slf4j;

import org.springframework.retry.RetryCallback;

import org.springframework.retry.RetryContext;

import org.springframework.retry.listener.RetryListenerSupport;

@Slf4j

public class DefaultListenerSupport extends RetryListenerSupport {

     @Override

     public <T, E extends Throwable> void close(RetryContext context,

                                                RetryCallback<T, E> callback, Throwable throwable) {

         log.info( "onClose" );

         super .close(context, callback, throwable);

     }

     public <T, E extends Throwable> void onError(RetryContext context,

                                                  RetryCallback<T, E> callback, Throwable throwable) {

         log.info( "onError" );

         super .onError(context, callback, throwable);

     public <T, E extends Throwable> boolean open(RetryContext context,

                                                  RetryCallback<T, E> callback) {

         log.info( "onOpen" );

         return super .open(context, callback);

}

把listener设置到retryTemplate中

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

package org.example;

 

import lombok.extern.slf4j.Slf4j;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.retry.backoff.FixedBackOffPolicy;

import org.springframework.retry.policy.SimpleRetryPolicy;

import org.springframework.retry.support.RetryTemplate;

@Configuration

@Slf4j

public class AppConfig {

     @Bean

     public RetryTemplate retryTemplate() {

         RetryTemplate retryTemplate = new RetryTemplate();

         SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); //设置重试策略

         retryPolicy.setMaxAttempts( 2 );

         retryTemplate.setRetryPolicy(retryPolicy);

         FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy(); //设置退避策略

         fixedBackOffPolicy.setBackOffPeriod(2000L);

         retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

         retryTemplate.registerListener( new DefaultListenerSupport()); //设置retryListener

         return retryTemplate;

     }

}

测试结果:

2021-01-08 10:48:05.663  INFO 20956 --- [           main] org.example.DefaultListenerSupport       : onOpen
2021-01-08 10:48:05.663  INFO 20956 --- [           main] org.example.RetryTemplateService         : do something...
2021-01-08 10:48:05.663  INFO 20956 --- [           main] org.example.DefaultListenerSupport       : onError
2021-01-08 10:48:07.664  INFO 20956 --- [           main] org.example.RetryTemplateService         : do something...
2021-01-08 10:48:07.664  INFO 20956 --- [           main] org.example.DefaultListenerSupport       : onError
2021-01-08 10:48:07.664  INFO 20956 --- [           main] org.example.RetryTemplateTest            : RecoveryCallback....
2021-01-08 10:48:07.664  INFO 20956 --- [           main] org.example.DefaultListenerSupport       : onClose

参考

spring retry

spring-projects / spring-retry

重试框架Spring retry实践

Spring 源码篇-Spring Retry

Guide to Spring Retry

后端---Spring-Retry框架介绍和基本开发

Spring-Retry重试实现原理

retry:基于spring-retry实现

Spring-Retry重试实现原理

usage-of-exceptionexpression-in-spring-retry

spring-retry注解方式使用(断路器,重试)

到此这篇关于Spring Boot中使用Spring-Retry重试框架的文章就介绍到这了,更多相关Spring Boot使用Spring-Retry内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

原文链接:https://HdhCmsTestcnblogs测试数据/ityml/p/16173432.html

查看更多关于Spring Boot中使用Spring Retry重试框架的操作方法的详细内容...

  阅读:17次