欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring Boot中使用Spring-Retry重試框架的實現(xiàn)

 更新時間:2022年04月24日 15:14:15   作者:ityml  
本文主要介紹了Spring Boot中使用Spring-Retry重試框架的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

Spring Retry提供了自動重新調(diào)用失敗的操作的功能。這在錯誤可能是暫時的(例如瞬時網(wǎng)絡(luò)故障)的情況下很有用。 從2.2.0版本開始,重試功能已從Spring Batch中撤出,成為一個獨立的新庫:Spring Retry

Maven依賴

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>
<!-- also need to add Spring AOP into our project-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
</dependency>

注解使用

開啟Retry功能

在啟動類中使用@EnableRetry注解

package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;

@SpringBootApplication
@EnableRetry
public class RetryApp {

    public static void main(String[] args) {
        SpringApplication.run(RetryApp.class, args);
    }
}

注解@Retryable

需要在重試的代碼中加入重試注解@Retryable

package org.example;

import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Service
@Slf4j
public class RetryService {

    @Retryable(value = IllegalAccessException.class)
    public void service1() throws IllegalAccessException {
        log.info("do something... {}", LocalDateTime.now());
        throw new IllegalAccessException("manual exception");
    }
}

默認(rèn)情況下,會重試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;  //默認(rèn)重試次數(shù)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(); //默認(rèn)的重試中的退避策略

	/**
	 * Specify an expression to be evaluated after the {@code SimpleRetryPolicy.canRetry()}
	 * returns true - can be used to conditionally suppress the retry. Only invoked after
	 * an exception is thrown. The root object for the evaluation is the last {@code Throwable}.
	 * Other beans in the context can be referenced.
	 * For example:
	 * <pre class=code>
	 *  {@code "message.contains('you can retry this')"}.
	 * </pre>
	 * and
	 * <pre class=code>
	 *  {@code "@someBean.shouldRetry(#root)"}.
	 * </pre>
	 * @return the expression.
	 * @since 1.2
	 */
	String exceptionExpression() default "";

	/**
	 * Bean names of retry listeners to use instead of default ones defined in Spring context
	 * @return retry listeners bean names
	 */
	String[] listeners() default {};

}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Backoff {

	/**
	 * Synonym for {@link #delay()}.
	 *
	 * @return the delay in milliseconds (default 1000)
	 */
	long value() default 1000; //默認(rèn)的重試間隔1秒

	/**
	 * A canonical backoff period. Used as an initial value in the exponential case, and
	 * as a minimum value in the uniform case.
	 * @return the initial or canonical backoff period in milliseconds (default 1000)
	 */
	long delay() default 0;

	/**
	 * The maximimum wait (in milliseconds) between retries. If less than the
	 * {@link #delay()} then the default of
	 * {@value org.springframework.retry.backoff.ExponentialBackOffPolicy#DEFAULT_MAX_INTERVAL}
	 * is applied.
	 *
	 * @return the maximum delay between retries (default 0 = ignored)
	 */
	long maxDelay() default 0;

	/**
	 * If positive, then used as a multiplier for generating the next delay for backoff.
	 *
	 * @return a multiplier to use to calculate the next backoff delay (default 0 =
	 * ignored)
	 */
	double multiplier() default 0;

	/**
	 * An expression evaluating to the canonical backoff period. Used as an initial value
	 * in the exponential case, and as a minimum value in the uniform case. Overrides
	 * {@link #delay()}.
	 * @return the initial or canonical backoff period in milliseconds.
	 * @since 1.2
	 */
	String delayExpression() default "";

	/**
	 * An expression evaluating to the maximimum wait (in milliseconds) between retries.
	 * If less than the {@link #delay()} then the default of
	 * {@value org.springframework.retry.backoff.ExponentialBackOffPolicy#DEFAULT_MAX_INTERVAL}
	 * is applied. Overrides {@link #maxDelay()}
	 *
	 * @return the maximum delay between retries (default 0 = ignored)
	 * @since 1.2
	 */
	String maxDelayExpression() default "";

	/**
	 * Evaluates to a vaule used as a multiplier for generating the next delay for
	 * backoff. Overrides {@link #multiplier()}.
	 *
	 * @return a multiplier expression to use to calculate the next backoff delay (default
	 * 0 = ignored)
	 * @since 1.2
	 */
	String multiplierExpression() default "";

	/**
	 * In the exponential case ({@link #multiplier()} &gt; 0) set this to true to have the
	 * backoff delays randomized, so that the maximum delay is multiplier times the
	 * previous delay and the distribution is uniform between the two values.
	 *
	 * @return the flag to signal randomization is required (default false)
	 */
	boolean random() default false;

}

我們來運行測試代碼

package org.example;


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class RetryServiceTest {

    @Autowired
    private RetryService retryService;

    @Test
    void testService1() throws IllegalAccessException {
        retryService.service1();
    }
}

運行結(jié)果如下:

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


java.lang.IllegalAccessException: manual exception

    at org.example.RetryService.service1(RetryService.java:19)
    at org.example.RetryService$$FastClassBySpringCGLIB$$c0995ddb.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:769)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
    at org.springframework.retry.interceptor.RetryOperationsInterceptor$1.doWithRetry(RetryOperationsInterceptor.java:91)
    at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:287)
    at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:164)
    at org.springframework.retry.interceptor.RetryOperationsInterceptor.invoke(RetryOperationsInterceptor.java:118)
    at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:153)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
    at org.example.RetryService$$EnhancerBySpringCGLIB$$499afa1d.service1(<generated>)
    at org.example.RetryServiceTest.testService1(RetryServiceTest.java:16)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:124)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:74)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:202)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)

可以看到重新執(zhí)行了3次service1()方法,然后間隔是1秒,然后最后還是重試失敗,所以拋出了異常

既然我們看到了注解@Retryable中有這么多參數(shù)可以設(shè)置,那我們就來介紹幾個常用的配置。

@Retryable(include = IllegalAccessException.class, maxAttempts = 5)
public void service2() throws IllegalAccessException {
    log.info("do something... {}", LocalDateTime.now());
    throw new IllegalAccessException("manual exception");
}

首先是maxAttempts,用于設(shè)置重試次數(shù)

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

從運行結(jié)果可以看到,方法執(zhí)行了5次。

下面來介紹maxAttemptsExpression的設(shè)置

@Retryable(value = IllegalAccessException.class, maxAttemptsExpression = "${maxAttempts}")
public void service3() throws IllegalAccessException {
    log.info("do something... {}", LocalDateTime.now());
    throw new IllegalAccessException("manual exception");
}

maxAttemptsExpression則可以使用表達(dá)式,比如上述就是通過獲取配置中maxAttempts的值,我們可以在application.yml設(shè)置。上述其實省略掉了SpEL表達(dá)式#{....},運行結(jié)果的話可以發(fā)現(xiàn)方法執(zhí)行了4次..

maxAttempts: 4

我們可以使用SpEL表達(dá)式

@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表達(dá)式

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

上面的表達(dá)式exceptionExpression = "message.contains('test')"的作用其實是獲取到拋出來exception的message(調(diào)用了getMessage()方法),然后判斷message的內(nèi)容里面是否包含了test字符串,如果包含的話就會執(zhí)行重試。所以如果調(diào)用方法的時候傳入的參數(shù)exceptionMessage中包含了test字符串的話就會執(zhí)行重試。

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

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

使用1.2.5之后的版本運行是沒有問題的

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

但是如果使用1.2.5版本之前包括1.2.5版本的話,運行的時候會報錯如下:

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

org.springframework.expression.spel.SpelEvaluationException: EL1001E: Type conversion problem, cannot convert from java.lang.String to java.lang.Boolean

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

還可以在表達(dá)式中執(zhí)行一個方法,前提是方法的類在spring容器中注冊了,@retryService其實就是獲取bean name為retryService的bean,然后調(diào)用里面的checkException方法,傳入的參數(shù)為#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的話表明會執(zhí)行重試,如果返回false則不會執(zhí)行重試
    }

運行結(jié)果:

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

當(dāng)然還有更多表達(dá)式的用法了...

	@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 重試等待策略, 默認(rèn)使用@Backoff注解。

我們先來看看這個@Backoffvalue屬性,用于設(shè)置重試間隔

 @Retryable(value = IllegalAccessException.class,
            backoff = @Backoff(value = 2000))
    public void service7() throws IllegalAccessException {
        log.info("do something... {}", LocalDateTime.now());
        throw new IllegalAccessException();
    }

運行結(jié)果可以看出來重試的間隔為2秒

2021-01-06 14:47:38.036  INFO 21116 --- [           main] org.example.RetryService                 : do something... 2021-01-06T14:47:38.036732600
2021-01-06 14:47:40.038  INFO 21116 --- [           main] org.example.RetryService                 : do something... 2021-01-06T14:47:40.037753600
2021-01-06 14:47:42.046  INFO 21116 --- [           main] org.example.RetryService                 : do something... 2021-01-06T14:47:42.046642900

java.lang.IllegalAccessException
    at org.example.RetryService.service7(RetryService.java:113)
...

接下來介紹@Backoffdelay屬性,它與value屬性不能共存,當(dāng)delay不設(shè)置的時候會去讀value屬性設(shè)置的值,如果delay設(shè)置的話則會忽略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();
    }

運行結(jié)果可以看出,重試的時間間隔為500ms

2021-01-06 15:22:42.271  INFO 13512 --- [           main] org.example.RetryService                 : do something... 2021-01-06T15:22:42.271504800
2021-01-06 15:22:42.772  INFO 13512 --- [           main] org.example.RetryService                 : do something... 2021-01-06T15:22:42.772234900
2021-01-06 15:22:43.273  INFO 13512 --- [           main] org.example.RetryService                 : do something... 2021-01-06T15:22:43.273246700

java.lang.IllegalAccessException
    at org.example.RetryService.service8(RetryService.java:121)

接下來我們來看``@Backoffmultiplier`的屬性, 指定延遲倍數(shù), 默認(rèn)為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設(shè)置為2,則表示第一次重試間隔為2s,第二次為4秒,第三次為8s

運行結(jié)果如下:

2021-01-06 15:58:07.458  INFO 23640 --- [           main] org.example.RetryService                 : do something... 2021-01-06T15:58:07.458245500
2021-01-06 15:58:09.478  INFO 23640 --- [           main] org.example.RetryService                 : do something... 2021-01-06T15:58:09.478681300
2021-01-06 15:58:13.478  INFO 23640 --- [           main] org.example.RetryService                 : do something... 2021-01-06T15:58:13.478921900
2021-01-06 15:58:21.489  INFO 23640 --- [           main] org.example.RetryService                 : do something... 2021-01-06T15:58:21.489240600

java.lang.IllegalAccessException
    at org.example.RetryService.service9(RetryService.java:128)
...

接下來我們來看看這個@BackoffmaxDelay屬性,設(shè)置最大的重試間隔,當(dāng)超過這個最大的重試間隔的時候,重試的間隔就等于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();
    }

運行結(jié)果:

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

當(dāng)@Retryable方法重試失敗之后,最后就會調(diào)用@Recover方法。用于@Retryable失敗時的“兜底”處理方法。 @Recover的方法必須要與@Retryable注解的方法保持一致,第一入?yún)橐卦嚨漠惓?,其他參?shù)與@Retryable保持一致,返回值也要一樣,否則無法執(zhí)行!

	@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

熔斷模式:指在具體的重試機(jī)制下失敗后打開斷路器,過了一段時間,斷路器進(jìn)入半開狀態(tài),允許一個進(jìn)入重試,若失敗再次進(jìn)入斷路器,成功則關(guān)閉斷路器,注解為@CircuitBreaker,具體包括熔斷打開時間、重置過期時間

	// openTimeout時間范圍內(nèi)失敗maxAttempts次數(shù)后,熔斷打開resetTimeout時長 
	@CircuitBreaker(openTimeout = 1000, resetTimeout = 3000, value = NullPointerException.class)
    public void circuitBreaker(int num) {
        log.info(" 進(jìn)入斷路器方法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("嘗試進(jìn)入斷路器方法,并觸發(fā)異常...");
        retryService.circuitBreaker(1);
        retryService.circuitBreaker(1);
        retryService.circuitBreaker(9);
        retryService.circuitBreaker(9);
        System.err.println("在openTimeout 1秒之內(nèi)重試次數(shù)為2次,未達(dá)到觸發(fā)熔斷, 斷路器依然閉合...");
        TimeUnit.SECONDS.sleep(1);
        System.err.println("超過openTimeout 1秒之后, 因為未觸發(fā)熔斷,所以重試次數(shù)重置,可以正常訪問...,繼續(xù)重試3次方法...");
        retryService.circuitBreaker(1);
        retryService.circuitBreaker(1);
        retryService.circuitBreaker(1);
        System.err.println("在openTimeout 1秒之內(nèi)重試次數(shù)為3次,達(dá)到觸發(fā)熔斷,不會執(zhí)行重試,只會執(zhí)行恢復(fù)方法...");
        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);

    }

運行結(jié)果:

嘗試進(jìn)入斷路器方法,并觸發(fā)異常...
2021-01-07 21:44:20.842  INFO 7464 --- [           main] org.example.RetryService                 :  進(jìn)入斷路器方法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                 :  進(jìn)入斷路器方法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                 :  進(jìn)入斷路器方法num=9
2021-01-07 21:44:20.845  INFO 7464 --- [           main] org.example.RetryService                 :  進(jìn)入斷路器方法num=9
在openTimeout 1秒之內(nèi)重試次數(shù)為2次,未達(dá)到觸發(fā)熔斷, 斷路器依然閉合...
超過openTimeout 1秒之后, 因為未觸發(fā)熔斷,所以重試次數(shù)重置,可以正常訪問...,繼續(xù)重試3次方法...
2021-01-07 21:44:21.846  INFO 7464 --- [           main] org.example.RetryService                 :  進(jìn)入斷路器方法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                 :  進(jìn)入斷路器方法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                 :  進(jìn)入斷路器方法num=1
2021-01-07 21:44:21.848  INFO 7464 --- [           main] org.example.RetryService                 : service retry after Recover => null
在openTimeout 1秒之內(nèi)重試次數(shù)為3次,達(dá)到觸發(fā)熔斷,不會執(zhí)行重試,只會執(zhí)行恢復(fù)方法...
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                 :  進(jìn)入斷路器方法num=9
2021-01-07 21:44:26.854  INFO 7464 --- [           main] org.example.RetryService                 :  進(jìn)入斷路器方法num=9
2021-01-07 21:44:26.855  INFO 7464 --- [           main] org.example.RetryService                 :  進(jìn)入斷路器方法num=9
2021-01-07 21:44:26.855  INFO 7464 --- [           main] org.example.RetryService                 :  進(jìn)入斷路器方法num=9
2021-01-07 21:44:26.856  INFO 7464 --- [           main] org.example.RetryService                 :  進(jìn)入斷路器方法num=9

RetryTemplate

RetryTemplate配置

package org.example;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;

@Configuration
public class AppConfig {
   
    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate retryTemplate = new RetryTemplate();

        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); //設(shè)置重試策略
        retryPolicy.setMaxAttempts(2);
        retryTemplate.setRetryPolicy(retryPolicy);


        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy(); //設(shè)置退避策略
        fixedBackOffPolicy.setBackOffPeriod(2000L);
        retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
		
        return retryTemplate;
    }
}

可以看到這些配置跟我們直接寫注解的方式是差不多的,這里就不過多的介紹了。。

使用RetryTemplate

package org.example;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.support.RetryTemplate;

@SpringBootTest
public class RetryTemplateTest {

    @Autowired
    private RetryTemplate retryTemplate;

    @Autowired
    private RetryTemplateService retryTemplateService;

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

RetryOperations定義重試的API,RetryTemplate是API的模板模式實現(xiàn),實現(xiàn)了重試和熔斷。提供的API如下:

package org.springframework.retry;

import org.springframework.retry.support.DefaultRetryState;

/**
 * Defines the basic set of operations implemented by {@link RetryOperations} to execute
 * operations with configurable retry behaviour.
 *
 * @author Rob Harrop
 * @author Dave Syer
 */
public interface RetryOperations {

	/**
	 * Execute the supplied {@link RetryCallback} with the configured retry semantics. See
	 * implementations for configuration details.
	 * @param <T> the return value
	 * @param retryCallback the {@link RetryCallback}
	 * @param <E> the exception to throw
	 * @return the value returned by the {@link RetryCallback} upon successful invocation.
	 * @throws E any {@link Exception} raised by the {@link RetryCallback} upon
	 * unsuccessful retry.
	 * @throws E the exception thrown
	 */
	<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback) throws E;

	/**
	 * Execute the supplied {@link RetryCallback} with a fallback on exhausted retry to
	 * the {@link RecoveryCallback}. See implementations for configuration details.
	 * @param recoveryCallback the {@link RecoveryCallback}
	 * @param retryCallback the {@link RetryCallback} {@link RecoveryCallback} upon
	 * @param <T> the type to return
	 * @param <E> the type of the exception
	 * @return the value returned by the {@link RetryCallback} upon successful invocation,
	 * and that returned by the {@link RecoveryCallback} otherwise.
	 * @throws E any {@link Exception} raised by the unsuccessful retry.
	 */
	<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback)
			throws E;

	/**
	 * A simple stateful retry. Execute the supplied {@link RetryCallback} with a target
	 * object for the attempt identified by the {@link DefaultRetryState}. Exceptions
	 * thrown by the callback are always propagated immediately so the state is required
	 * to be able to identify the previous attempt, if there is one - hence the state is
	 * required. Normal patterns would see this method being used inside a transaction,
	 * where the callback might invalidate the transaction if it fails.
	 *
	 * See implementations for configuration details.
	 * @param retryCallback the {@link RetryCallback}
	 * @param retryState the {@link RetryState}
	 * @param <T> the type of the return value
	 * @param <E> the type of the exception to return
	 * @return the value returned by the {@link RetryCallback} upon successful invocation,
	 * and that returned by the {@link RecoveryCallback} otherwise.
	 * @throws E any {@link Exception} raised by the {@link RecoveryCallback}.
	 * @throws ExhaustedRetryException if the last attempt for this state has already been
	 * reached
	 */
	<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RetryState retryState)
			throws E, ExhaustedRetryException;

	/**
	 * A stateful retry with a recovery path. Execute the supplied {@link RetryCallback}
	 * with a fallback on exhausted retry to the {@link RecoveryCallback} and a target
	 * object for the retry attempt identified by the {@link DefaultRetryState}.
	 * @param recoveryCallback the {@link RecoveryCallback}
	 * @param retryState the {@link RetryState}
	 * @param retryCallback the {@link RetryCallback}
	 * @param <T> the return value type
	 * @param <E> the exception type
	 * @see #execute(RetryCallback, RetryState)
	 * @return the value returned by the {@link RetryCallback} upon successful invocation,
	 * and that returned by the {@link RecoveryCallback} otherwise.
	 * @throws E any {@link Exception} raised by the {@link RecoveryCallback} upon
	 * unsuccessful retry.
	 */
	<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback,
			RetryState retryState) throws E;

}

下面主要介紹一下RetryTemplate配置的時候,需要設(shè)置的重試策略和退避策略

RetryPolicy

RetryPolicy是一個接口, 然后有很多具體的實現(xiàn),我們先來看看它的接口中定義了什么方法

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

}

我們來看看他有什么具體的實現(xiàn)類

  • SimpleRetryPolicy 默認(rèn)最多重試3次
  • TimeoutRetryPolicy 默認(rèn)在1秒內(nèi)失敗都會重試
  • ExpressionRetryPolicy 符合表達(dá)式就會重試
  • CircuitBreakerRetryPolicy 增加了熔斷的機(jī)制,如果不在熔斷狀態(tài),則允許重試
  • CompositeRetryPolicy 可以組合多個重試策略
  • NeverRetryPolicy 從不重試(也是一種重試策略哈)
  • AlwaysRetryPolicy 總是重試
  • 等等...

BackOffPolicy

看一下退避策略,退避是指怎么去做下一次的重試,在這里其實就是等待多長時間。

  • FixedBackOffPolicy 默認(rèn)固定延遲1秒后執(zhí)行下一次重試
  • ExponentialBackOffPolicy 指數(shù)遞增延遲執(zhí)行重試,默認(rèn)初始0.1秒,系數(shù)是2,那么下次延遲0.2秒,再下次就是延遲0.4秒,如此類推,最大30秒。
  • ExponentialRandomBackOffPolicy 在上面那個策略上增加隨機(jī)性
  • UniformRandomBackOffPolicy 這個跟上面的區(qū)別就是,上面的延遲會不停遞增,這個只會在固定的區(qū)間隨機(jī)
  • StatelessBackOffPolicy 這個說明是無狀態(tài)的,所謂無狀態(tài)就是對上次的退避無感知,從它下面的子類也能看出來
  • 等等...

RetryListener

listener可以監(jiān)聽重試,并執(zhí)行對應(yīng)的回調(diào)方法

package org.springframework.retry;

/**
 * Interface for listener that can be used to add behaviour to a retry. Implementations of
 * {@link RetryOperations} can chose to issue callbacks to an interceptor during the retry
 * lifecycle.
 *
 * @author Dave Syer
 *
 */
public interface RetryListener {

	/**
	 * Called before the first attempt in a retry. For instance, implementers can set up
	 * state that is needed by the policies in the {@link RetryOperations}. The whole
	 * retry can be vetoed by returning false from this method, in which case a
	 * {@link TerminatedRetryException} will be thrown.
	 * @param <T> the type of object returned by the callback
	 * @param <E> the type of exception it declares may be thrown
	 * @param context the current {@link RetryContext}.
	 * @param callback the current {@link RetryCallback}.
	 * @return true if the retry should proceed.
	 */
	<T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback);

	/**
	 * Called after the final attempt (successful or not). Allow the interceptor to clean
	 * up any resource it is holding before control returns to the retry caller.
	 * @param context the current {@link RetryContext}.
	 * @param callback the current {@link RetryCallback}.
	 * @param throwable the last exception that was thrown by the callback.
	 * @param <E> the exception type
	 * @param <T> the return value
	 */
	<T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);

	/**
	 * Called after every unsuccessful attempt at a retry.
	 * @param context the current {@link RetryContext}.
	 * @param callback the current {@link RetryCallback}.
	 * @param throwable the last exception that was thrown by the callback.
	 * @param <T> the return value
	 * @param <E> the exception to throw
	 */
	<T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);

}

使用如下:

自定義一個Listener

package org.example;

import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.listener.RetryListenerSupport;

@Slf4j
public class DefaultListenerSupport extends RetryListenerSupport {
    @Override
    public <T, E extends Throwable> void close(RetryContext context,
                                               RetryCallback<T, E> callback, Throwable throwable) {
        log.info("onClose");
        super.close(context, callback, throwable);
    }

    @Override
    public <T, E extends Throwable> void onError(RetryContext context,
                                                 RetryCallback<T, E> callback, Throwable throwable) {
        log.info("onError");
        super.onError(context, callback, throwable);
    }

    @Override
    public <T, E extends Throwable> boolean open(RetryContext context,
                                                 RetryCallback<T, E> callback) {
        log.info("onOpen");
        return super.open(context, callback);
    }
}

把listener設(shè)置到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(); //設(shè)置重試策略
        retryPolicy.setMaxAttempts(2);
        retryTemplate.setRetryPolicy(retryPolicy);


        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy(); //設(shè)置退避策略
        fixedBackOffPolicy.setBackOffPeriod(2000L);
        retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
        retryTemplate.registerListener(new DefaultListenerSupport()); //設(shè)置retryListener
        return retryTemplate;
    }
}

測試結(jié)果:

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

參考

spring retry

spring-projects/spring-retry

重試框架Spring retry實踐

Spring 源碼篇-Spring Retry

Guide to Spring Retry

后端---Spring-Retry框架介紹和基本開發(fā)

Spring-Retry重試實現(xiàn)原理

retry:基于spring-retry實現(xiàn)

Spring-Retry重試實現(xiàn)原理

usage-of-exceptionexpression-in-spring-retry

spring-retry注解方式使用(斷路器,重試)

到此這篇關(guān)于Spring Boot中使用Spring-Retry重試框架的實現(xiàn)的文章就介紹到這了,更多相關(guān)Spring-Retry重試內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java使用RSA工具進(jìn)行信息加解密

    java使用RSA工具進(jìn)行信息加解密

    我們在開發(fā)中需要對用戶敏感數(shù)據(jù)進(jìn)行加解密,比如密碼等信息,這篇文章主要為大家詳細(xì)介紹了java如何使用RSA工具進(jìn)行信息加解密,感興趣的小伙伴可以了解下
    2023-12-12
  • SpringBoot工程啟動順序與自定義監(jiān)聽超詳細(xì)講解

    SpringBoot工程啟動順序與自定義監(jiān)聽超詳細(xì)講解

    這篇文章主要介紹了SpringBoot工程啟動順序與自定義監(jiān)聽,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2022-11-11
  • Spring切入點表達(dá)式配置過程圖解

    Spring切入點表達(dá)式配置過程圖解

    這篇文章主要介紹了Spring切入點表達(dá)式配置過程圖解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-08-08
  • springboot整合spring-retry的實現(xiàn)示例

    springboot整合spring-retry的實現(xiàn)示例

    本文將結(jié)合實例代碼,介紹springboot整合spring-retry的實現(xiàn)示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-06-06
  • Java?Exception異常全方面分析

    Java?Exception異常全方面分析

    異常就是不正常,比如當(dāng)我們身體出現(xiàn)了異常我們會根據(jù)身體情況選擇喝開水、吃藥、看病、等?異常處理方法。?java異常處理機(jī)制是我們java語言使用異常處理機(jī)制為程序提供了錯誤處理的能力,程序出現(xiàn)的錯誤,程序可以安全的退出,以保證程序正常的運行等
    2022-03-03
  • 處理Log4j2不能打印行號的問題(AsyncLogger)

    處理Log4j2不能打印行號的問題(AsyncLogger)

    這篇文章主要介紹了處理Log4j2不能打印行號的問題(AsyncLogger),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • java讀取csv文件內(nèi)容示例代碼

    java讀取csv文件內(nèi)容示例代碼

    這篇文章主要介紹了java讀取csv文件內(nèi)容的示例,大家參考使用
    2013-12-12
  • Java 8 中 Map 騷操作之 merge() 的使用方法

    Java 8 中 Map 騷操作之 merge() 的使用方法

    本文簡單介紹了一下Map.merge()的方法,除此之外,Java 8 中的HashMap實現(xiàn)方法使用了TreeNode和 紅黑樹,原理很相似,今天通過本文給大家介紹Java 8 中 Map 騷操作之 merge() 的用法 ,需要的朋友參考下吧
    2021-07-07
  • Java異常類型以及處理實例詳解

    Java異常類型以及處理實例詳解

    在程序設(shè)計中,進(jìn)行異常處理是非常關(guān)鍵和重要的一部分,一個程序的異常處理框架的好壞直接影響到整個項目的代碼質(zhì)量以及后期維護(hù)成本和難度,這篇文章主要給大家介紹了關(guān)于Java異常類型以及處理的相關(guān)資料,需要的朋友可以參考下
    2021-07-07
  • java Future 接口使用方法詳解

    java Future 接口使用方法詳解

    這篇文章主要介紹了java Future 接口使用方法詳解,F(xiàn)uture接口是Java線程Future模式的實現(xiàn),可以來進(jìn)行異步計算的相關(guān)資料,需要的朋友可以參考下
    2017-03-03

最新評論