# spring-retry-demo
**Repository Path**: luweiz/spring-retry-demo
## Basic Information
- **Project Name**: spring-retry-demo
- **Description**: No description available
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 4
- **Created**: 2022-02-22
- **Last Updated**: 2022-02-22
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
[TOC]
# Spring Boot中使用Spring-Retry重试框架
> Spring Retry提供了自动重新调用失败的操作的功能。这在错误可能是暂时的(例如瞬时网络故障)的情况下很有用。 从2.2.0版本开始,重试功能已从Spring Batch中撤出,成为一个独立的新库:Spring Retry
## Maven依赖
```xml
org.springframework.retryspring-retryorg.springframeworkspring-aspects
```
## 注解使用
### 开启Retry功能
在启动类中使用@EnableRetry注解
```java
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`
```java
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`中看到
```java
@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 {};
}
```
```java
@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;
}
```
我们来运行测试代码
```java
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();
}
}
```
运行结果如下:
```bash
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`中有这么多参数可以设置,那我们就来介绍几个常用的配置。
```java
@Retryable(include = IllegalAccessException.class, maxAttempts = 5)
public void service2() throws IllegalAccessException {
log.info("do something... {}", LocalDateTime.now());
throw new IllegalAccessException("manual exception");
}
```
首先是`maxAttempts`,用于设置重试次数
```bash
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`的设置
```java
@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次..
```yml
maxAttempts: 4
```
我们可以使用SpEL表达式
```java
@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表达式
```java
@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之后的版本运行是没有问题的
```xml
org.springframework.retryspring-retry1.3.0
```
但是如果使用1.2.5版本之前包括1.2.5版本的话,运行的时候会报错如下:
```bash
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对象。一样的也是可以省略`#{...}`
```java
@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则不会执行重试
}
```
运行结果:
```bash
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
```
当然还有更多表达式的用法了...
```java
@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);
}
```
```java
@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);
}
```
```java
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`
```java
@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`属性,用于设置重试间隔
```java
@Retryable(value = IllegalAccessException.class,
backoff = @Backoff(value = 2000))
public void service7() throws IllegalAccessException {
log.info("do something... {}", LocalDateTime.now());
throw new IllegalAccessException();
}
```
运行结果可以看出来重试的间隔为2秒
```bash
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`属性
```java
@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
```bash
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。
```java
@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
运行结果如下:
```bash
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`的值
```java
@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();
}
```
运行结果:
```bash
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`保持一致,返回值也要一样,否则无法执行!
```java
@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`,具体包括熔断打开时间、重置过期时间
```java
// 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());
}
```
测试方法
```java
@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);
}
```
运行结果:
```bash
尝试进入断路器方法,并触发异常...
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配置
```java
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
```java
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