Spring Retry 在SpringBoot 中的应用


Spring Boot中使用Spring-Retry重试框架

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

Maven依赖

<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注解

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

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中看到

@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). 	 * @return exception types to retry 	 */ 	Class<? extends Throwable>[] include() default {};  	/** 	 * Exception types that are not retryable. Defaults to empty (and if includes is also 	 * empty all exceptions are retried). 	 * 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 	 * <pre class=code> 	 *  {@code "@someBean.shouldRetry(#root)"}. 	 * </pre> 	 * @return the expression. 	 * @since 1.2 	 */ 	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 {};  } 
@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 	 * {@value org.springframework.retry.backoff.ExponentialBackOffPolicy#DEFAULT_MAX_INTERVAL} 	 * is applied. Overrides {@link #maxDelay()} 	 * 	 * @return the maximum delay between retries (default 0 = ignored) 	 * @since 1.2 	 */ 	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) 	 * @since 1.2 	 */ 	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;  }  

我们来运行测试代码

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   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$CglibMethodInvocation.proceed(CglibAopProxy.java:747) 	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.commons.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.ThrowableCollector.execute(ThrowableCollector.java:73) 	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.ThrowableCollector.execute(ThrowableCollector.java:73) 	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.ThrowableCollector.execute(ThrowableCollector.java:73) 	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.ThrowableCollector.execute(ThrowableCollector.java:73) 	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.ThrowableCollector.execute(ThrowableCollector.java:73) 	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.ThrowableCollector.execute(ThrowableCollector.java:73) 	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 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中有这么多参数可以设置,那我们就来介绍几个常用的配置。

@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的设置

@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次..

maxAttempts: 4 

我们可以使用SpEL表达式

@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表达式

@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之后的版本运行是没有问题的

<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.common.ExpressionUtils.convertTypedValue(ExpressionUtils.java:57) 	at org.springframework.expression.common.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.commons.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.ThrowableCollector.execute(ThrowableCollector.java:73) 	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.ThrowableCollector.execute(ThrowableCollector.java:73) 	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.ThrowableCollector.execute(ThrowableCollector.java:73) 	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.ThrowableCollector.execute(ThrowableCollector.java:73) 	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.ThrowableCollector.execute(ThrowableCollector.java:73) 	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.ThrowableCollector.execute(ThrowableCollector.java:73) 	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 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对象。一样的也是可以省略#{...}

    @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 {         log.info("do something... {}", LocalDateTime.now());         throw new IllegalAccessException(exceptionMessage);     }          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  

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

	@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);     }  
    @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);     } 
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

  @Retryable(exclude = MyException.class)     public void service6(String exceptionMessage) throws MyException {         log.info("do something... {}", LocalDateTime.now());         throw new MyException(exceptionMessage);     }      

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

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

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

 @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) ... 

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

@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) 

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

@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) ... 

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

@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保持一致,返回值也要一样,否则无法执行!

	@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 {         log.info("do something... {}", LocalDateTime.now());         return 1 / 0;     }       @Recover     public int recover12(ArithmeticException e) {         log.info("service retry after Recover => {}", e.getMessage());         return 0;     }       //=========================      @Retryable(value = ArithmeticException.class)     public int service13(String message) throws IllegalAccessException {         log.info("do something... {},{}", message, LocalDateTime.now());         return 1 / 0;     }       @Recover     public int recover13(ArithmeticException e, String message) {         log.info("{},service retry after Recover => {}", message, e.getMessage());         return 0;     } 

注解@CircuitBreaker

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

	// 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());     } 

测试方法

	@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配置

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

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;      @Autowired     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;             }         });     }          @Test     void test2() throws IllegalAccessException {         retryTemplate.execute(new RetryCallback<Object, IllegalAccessException>() {             @Override             public Object doWithRetry(RetryContext context) throws IllegalAccessException {                 retryTemplateService.service1();                 return null;             }         }, new RecoveryCallback<Object>() {             @Override             public Object recover(RetryContext context) throws Exception {                 log.info("RecoveryCallback....");                 return null;             }         });     } }  

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

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 retryCallback the {@link RetryCallback} 	 * @param retryState the {@link RetryState} 	 * @param <T> the type of the return value 	 * @param <E> the type of the exception to return 	 * @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 {@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 recoveryCallback the {@link RecoveryCallback} 	 * @param retryState the {@link RetryState} 	 * @param retryCallback the {@link RetryCallback} 	 * @param <T> the return value type 	 * @param <E> the exception type 	 * @see #execute(RetryCallback, RetryState) 	 * @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 {@link RecoveryCallback} upon 	 * unsuccessful retry. 	 */ 	<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback, 			RetryState retryState) throws E;  } 

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

RetryPolicy

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

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);  }  

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

Spring Retry 在SpringBoot 中的应用

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

BackOffPolicy

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

Spring Retry 在SpringBoot 中的应用

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

RetryListener

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

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 context the current {@link RetryContext}. 	 * @param callback the current {@link RetryCallback}. 	 * @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 context the current {@link RetryContext}. 	 * @param callback the current {@link RetryCallback}. 	 * @param throwable the last exception that was thrown by the callback. 	 * @param <T> the return value 	 * @param <E> the exception to throw 	 */ 	<T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);  } 

使用如下:

自定义一个Listener

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);     }      @Override     public <T, E extends Throwable> void onError(RetryContext context,                                                  RetryCallback<T, E> callback, Throwable throwable) {         log.info("onError");         super.onError(context, callback, throwable);     }      @Override     public <T, E extends Throwable> boolean open(RetryContext context,                                                  RetryCallback<T, E> callback) {         log.info("onOpen");         return super.open(context, callback);     } } 

把listener设置到retryTemplate中

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注解方式使用(断路器,重试)

发表评论

相关文章