范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

SpringRetry在SpringBoot中的应用

  Spring Retry提供了自动重新调用失败的操作的功能。这在错误可能是暂时的(例如瞬时网络故障)的情况下很有用。 从2.2.0版本开始,重试功能已从Spring Batch中撤出,成为一个独立的新库:Spring Retry Maven依赖     org.springframework.retry     spring-retry        org.springframework     spring-aspects  注解使用开启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: 	 * 
 	 *  {@code "message.contains("you can retry this")"}. 	 * 
* and *
 	 *  {@code "@someBean.shouldRetry(#root)"}. 	 * 
* @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()} > 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() 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() 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之后的版本运行是没有问题的 org.springframework.retry spring-retry 1.3.0   但是如果使用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() 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 注解。   我们先来看看这个 @Backoff 的value 属性,用于设置重试间隔 @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 属性@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。@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 的值@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 RetryTemplateRetryTemplate配置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; } }   可以看到这些配置跟我们直接写注解的方式是差不多的,这里就不过多的介绍了… 使用RetryTemplatepackage 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() { @Override public Object doWithRetry(RetryContext context) throws IllegalAccessException { retryTemplateService.service1(); return null; } }); } @Test void test2() throws IllegalAccessException { retryTemplate.execute(new RetryCallback() { @Override public Object doWithRetry(RetryContext context) throws IllegalAccessException { retryTemplateService.service1(); return null; } }, new RecoveryCallback() { @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 the return value * @param retryCallback the {@link RetryCallback} * @param 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 execute(RetryCallback 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 the type to return * @param 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 execute(RetryCallback retryCallback, RecoveryCallback 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 the type of the return value * @param 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 execute(RetryCallback 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 the return value type * @param 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 execute(RetryCallback retryCallback, RecoveryCallback 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); }   我们来看看他有什么具体的实现类 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可以监听重试,并执行对应的回调方法 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 the type of object returned by the callback * @param 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. */ boolean open(RetryContext context, RetryCallback 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 the exception type * @param the return value */ void close(RetryContext context, RetryCallback 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 the return value * @param the exception to throw */ void onError(RetryContext context, RetryCallback 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 void close(RetryContext context, RetryCallback callback, Throwable throwable) { log.info("onClose"); super.close(context, callback, throwable); } @Override public void onError(RetryContext context, RetryCallback callback, Throwable throwable) { log.info("onError"); super.onError(context, callback, throwable); } @Override public boolean open(RetryContext context, RetryCallback 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   文章来自https://www.cnblogs.com/ityml/p/16173432.html
富力100亿出售物业,大股东自掏80亿自救,难掩390亿资金缺口面临债务即将到期的富力,为了解决当前的燃眉之急,准备以100亿的价格出售其物业。就连公司的大股东都自掏80亿腰包挽救,但即便如此,该公司也依旧存在390亿的资金缺口。那么,它将如何智慧加身,畅玩投屏爱奇艺电视果5SPLUS智慧投屏体验报告随着科技的发展,显示设备的尺寸不断增大,同时与移动端设备互联的需求越来越旺盛,投屏这个小玩意已经被越来越多地应用在各个场景中。投屏最早出现,是作为一个中介设备,功能就是通过有线或无09Java线程(上)Java线程的生命周期Java并发编程领域实现并发程序的主要手段就是多线程,线程是操作系统的一个概念。虽然各种语言都进行了封装,但是万变不离其操作系统。Java语言里面的线程本质上就是操作系统的线程,他学生党笔记本别瞎买,这款可以兼顾性能屏幕和性价比,4年不卡对于结束高考后的准大学生来说,买一款合适的笔记本是非常重要的,因为笔记本不仅仅是娱乐工具,更是生产力工具,当年我也是利用笔记本打开了新世界的大门,学习了专业的工具,有了自己的一技之腾讯游戏黑鲨手机3术业有专攻跑分王手机才是专业玩家的选择最近,全球首款5G游戏手机腾讯黑鲨游戏手机3刚一发布,就赢得了媒体和用户的一致好评。特别是游戏爱好者们,早早地为腾讯黑鲨游戏手机3打上了游戏神机上分利器等称号。放眼市面热门的旗舰机13万多的B级车,价格便宜却卖不动,现代名图为何月销仅几百辆?虽然国内乘用车市场在不断下滑,但根据乘联会统计的数据显示,中高端车型的销量却在一直攀升,就比如前段时间的雅阁凯美瑞等车型,销量都达到了2万辆左右,在销量上直逼很多紧凑轿车,然而并不平民玩家的利器,雷柏VT30幻彩RGB游戏鼠标对于喜欢玩游戏的人来说,鼠标的好坏决定了操作的流畅性,甚至可以影响战局的敲定。在全民竞技的时代,无论男女老少,就算玩LOL都会要求配个好点的鼠标。雷柏,在外设方面也有不少建树,大家上汽大通MAXUSMIFA量产车曝光!定位中大型纯电MPV,将下半年生产随着越来越多人响应三胎政策,MPV等七座车型受到更多人的关注,伴随着全球新能源汽车的发展,同时也不少企业改变发展战略。因此,上汽大通MAXUS于今年上海车展全球首秀的MIFA概念车新版汽车三包规定即将实施,7天内可退换车合理吗?在旧版汽车三包规定服役了七年半之后,国家市场监督管理总局在不久前发布了新版汽车三包规定,新规将会在2022年1月1日正式实施。对于新政策的实施,不少车主的还是保留了自己的意见,希望继鸿星尔克野性消费后,你愿意为国产汽车花多少钱?曾几何时,在大家的印象中,自主汽车的标签就是廉价低质。不过,随着市场竞争愈发激烈产品迭代更新,自主品牌已经渐渐获得了消费者的认可。其中,在自主品牌产品力提高的情况下,国产汽车的售价取消大小周易,取消周报难又一家公司宣布取消大小周。9月1日,小鹏汽车向全员发布了相关通知。自6月份快手宣布取消大小周以来,越来越多公司正在加入进来。夺回一点打工自由的年轻人,又将目光投向了争议不断的周报。
打响第一枪,比亚迪停止生产燃油车,在下怎样一盘大棋这两年的时间里,比亚迪在新能源车市场中的销量迎来了爆发式的增长,大家都是有目共睹的。去年全年,比亚迪新能源乘用车累计销量达到了593745辆,毫无悬念地力压特斯拉登顶中国新能源乘用国产X86新处理器将采用7nm工艺支持DDR5自从国外对我们实行卡脖子的政策后,在芯片这件事上加速进阶就成为了国家级战略,而上海兆芯推出的KX6000是一款国产x86处理器,采用16nm工艺,最高8核架构,代号为陆家嘴(Luj重磅!河南发现自然界新矿物来源大河报河南省地矿局3日消息称,中国科研团队在河南省桐柏县银洞坡金矿发现并命名了一种自然界新矿物空铁黝银矿。2022年是国际矿物学协会认定的矿物学年,此次发现的空铁黝银矿是202并发编程同步异步阻塞非阻塞同步执行一个进程在执行某个任务时,另外一个进程必须等待其执行完毕,才能继续执行异步执行一个进程在执行某个任务时,另外一个进程无需等待其执行完毕,就可以继续执行,当有消息返回时,系统手机稳定器是智商税?奥川表示不服,实测告诉你值在哪随着短视频的普及,越来越多的普通用户也加入到了创作者阵营,有些人或许只是拿着手机随便拍拍,但还有一些人感受到乐趣之后,会想办法提升自己的视频拍摄水平。同为新手过来的我觉得,想要提升手机跑分排行榜出炉!天玑9000芯片手机,排名未进入前三安兔兔在近日给出了智能手机跑分排行榜,榜单显示目前跑分最高的三款智能手机分别是红魔7Pro屏下游戏手机iQOO9iQOO9Pro,这三款手机均搭载骁龙8Gen1芯片,第一名的手机还这款手机暴露了我的年龄你买的第一部手机是什么牌子的,怎么样我的第一部手机是摩托罗拉GC87C,1998年买的。图片来自网络。当时我辞职离开了工作了5年的公司,9个月后被董事长请回来,他给我买了一部手机和三星GalaxyA53初体验,这么好看且全能的手机,只卖3299元贵吗?尽管三星在国内市场的份额已经跌进了Other里,但随着三星渐渐深入本地化工作,这两年产品销量在国内也算是有了起色,只不过不是很明显罢了。来到2022年三星推出了年度旗舰Galaxy可观测宇宙之外到底是什么?是整个宇宙的终点,还是多重宇宙宇宙究竟有多大?可观测宇宙之外又有什么?这个看似简短的两个问题却困扰了人类长达几千年之久。根据科学家研究表明,目前我们所处的宇宙正在以一个惊人的速度向外膨胀。通过分析哈勃空间望远镜迄今为止,科学家已经揭示了多少物质世界的奥秘?围绕世界有什么世界是如何变成这样的这两个问题,2004年诺贝尔物理学奖得主维尔切克在万物原理中,揭示了十项洞见,告诉人们可以从物理世界的研究中学到哪些最基本的原理了解发现它们的思维古生物学家提出新理论解释为什么霸王龙手臂短得离谱在本期波兰古生物学杂志上发表的一篇新论文中,古生物学家凯文帕迪安教授提出了一个新的假说。当一群霸王龙用它们巨大的头和碎骨的牙齿扑向尸体时,霸王龙的手臂会缩小以防止意外或截肢。例如,