Spring-Retry

 

https://github.com/spring-projects/spring-retry

This project provides declarative retry support for Spring applications. It is used in Spring Batch, Spring Integration, and others. Imperative retry is also supported for explicit usage.

Quick Start

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.springframework.retry/spring-retry -->
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>2.0.5</version>
</dependency>

  

This section provides a quick introduction to getting started with Spring Retry. It includes a declarative example and an imperative example.

Declarative Example

The following example shows how to use Spring Retry in its declarative style:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
@EnableRetry
public class Application {
 
}
 
@Service
class Service {
    @Retryable(retryFor = RemoteAccessException.class)
    public void service() {
        // ... do something
    }
    @Recover
    public void recover(RemoteAccessException e) {
       // ... panic
    }
}

  

This example calls the service method and, if it fails with a RemoteAccessException, retries (by default, up to three times), and then tries the recover method if unsuccessful. There are various options in the @Retryable annotation attributes for including and excluding exception types, limiting the number of retries, and setting the policy for backoff.

The declarative approach to applying retry handling by using the @Retryable annotation shown earlier has an additional runtime dependency on AOP classes. For details on how to resolve this dependency in your project, see the 'Java Configuration for Retry Proxies' section.

Imperative Example

The following example shows how to use Spring Retry in its imperative style (available since version 1.3):

1
2
3
4
5
6
7
8
9
RetryTemplate template = RetryTemplate.builder()
                .maxAttempts(3)
                .fixedBackoff(1000)
                .retryOn(RemoteAccessException.class)
                .build();
 
template.execute(ctx -> {
    // ... do something
});

 

For versions prior to 1.3, see the examples in the RetryTemplate section.

 

Building

Spring Retry requires Java 1.7 and Maven 3.0.5 (or greater). To build, run the following Maven command:

 

Features and API

This section discusses the features of Spring Retry and shows how to use its API.

Using RetryTemplate

To make processing more robust and less prone to failure, it sometimes helps to automatically retry a failed operation, in case it might succeed on a subsequent attempt. 

Errors that are susceptible to this kind of treatment are transient in nature.

For example, a remote call to a web service or an RMI service that fails because of a network glitch or a DeadLockLoserException in a database update may resolve itself after a short wait.

To automate the retry of such operations, Spring Retry has the RetryOperations strategy.

The RetryOperations interface definition follows:

为了使处理更加稳健且不易失败,有时自动重试失败的操作会有所帮助,以防后续尝试可能成功。

容易受到这种处理的错误本质上是暂时的

例如,对 Web 服务或 RMI 服务的远程调用由于网络故障或数据库更新中的 DeadLockLoserException 而失败,可能会在短暂等待后自行解决。

为了自动重试此类操作,Spring Retry 具有 RetryOperations 策略

RetryOperations 接口定义如下:

1
2
3
4
5
6
7
8
9
10
11
public interface RetryOperations {
 
    <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback) throws E;
 
    <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback) throws E;
 
    <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RetryState retryState) throws E, ExhaustedRetryException;
 
    <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState retryState) throws E;
 
}

  

The basic callback is a simple interface that lets you insert some business logic to be retried:

1
2
3
4
5
public interface RetryCallback<T, E extends Throwable> {
 
    T doWithRetry(RetryContext context) throws E;
 
}

  

The callback is tried, and, if it fails (by throwing an Exception), it is retried until either it is successful or the implementation decides to abort.

There are a number of overloaded execute methods in the RetryOperations interface, to deal with various use cases for recovery when all retry attempts are exhausted and to deal with retry state, which lets clients and implementations store information between calls (more on this later).

尝试回调,如果通过抛出异常失败,则会重试,直到成功或实现决定中止。

RetryOperations 接口中有许多重载的execute方法,用于在所有重试尝试都用尽时处理各种恢复用例,并处理重试状态,这允许客户端和实现在调用之间存储信息(稍后详细介绍)。

 

The simplest general purpose implementation of RetryOperations is RetryTemplate. 最简单的实现 是 RetryTemplate

 

The following example shows how to use it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RetryTemplate template = new RetryTemplate();
 
TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
policy.setTimeout(30000L);
 
template.setRetryPolicy(policy);
 
MyObject result = template.execute(new RetryCallback<MyObject, Exception>() {
 
    public MyObject doWithRetry(RetryContext context) {
        // Do stuff that might fail, e.g. webservice operation
        return result;
    }
 
});

  

In the preceding example, we execute a web service call and return the result to the user.If that call fails, it is retried until a timeout is reached.

 

Since version 1.3, fluent configuration of RetryTemplate is also available, as follows:  从1.3版本起,RetryTemplate模版也支持fluent configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
RetryTemplate.builder()
      .maxAttempts(10)
      .exponentialBackoff(100, 2, 10000)
      .retryOn(IOException.class)
      .traversingCauses()
      .build();
 
RetryTemplate.builder()
      .fixedBackoff(10)
      .withinMillis(3000)
      .build();
 
RetryTemplate.builder()
      .infiniteRetry()
      .retryOn(IOException.class)
      .uniformRandomBackoff(1000, 3000)
      .build();

  

Using RetryContext

The method parameter for the RetryCallback is a RetryContext.    RetryCallback的方法参数类型是RetryContext;

Many callbacks ignore the context.    大部分情况是忽略这个Context;

However, if necessary, you can use it as an attribute bag to store data for the duration of the iteration.    如果有必要,可以用来存储迭代期间的数据;

It also has some useful properties, such as retryCount.  RetryContext有非常有用的属性,如retryCount;

 

RetryContext has a parent context if there is a nested retry in progress in the same thread.  如果在同一个线程中 进行嵌套重试,RetryContext有个父 Context;

The parent context is occasionally useful for storing data that needs to be shared between calls to execute.  父Context可以用作调用之间的共享数据;

 

If you don't have access to the context directly, you can obtain the current context within the scope of the retries by calling RetrySynchronizationManager.getContext().

By default, the context is stored in a ThreadLocal.

JEP 444 recommends that ThreadLocal should be avoided when using virtual threads, available in Java 21 and beyond.

To store the contexts in a Map instead of a ThreadLocal, call RetrySynchronizationManager.setUseThreadLocal(false).

如果无法直接 获得 Context,可以通过RetrySynchronizationManager.getContext()获得;

Context默认存储在ThreadLocal中;

对于Java21及以上,不要存储在ThreadLocal中,可以使用Map代替;

 

Using RecoveryCallback

When a retry is exhausted, the RetryOperations can pass control to a different callback: RecoveryCallback.  当重试次数耗尽,重试操作可以传递给不同的回调:RecoveryCallback

To use this feature, clients can pass in the callbacks together to the same method, as the following example shows:  

1
2
3
4
5
6
7
8
9
MyObject myObject = template.execute(new RetryCallback<MyObject, Exception>() {
    public MyObject doWithRetry(RetryContext context) {
        // business logic here
    },
  new RecoveryCallback<MyObject>() {
    MyObject recover(RetryContext context) throws Exception {
          // recover logic here
    }
});

  

If the business logic does not succeed before the template decides to abort, the client is given the chance to do some alternate processing through the recovery callback.

如果在template决定放弃前,业务逻辑还没执行完,还可以通过 recovery callback做一些其他处理;

 

Declarative Retry

Sometimes, you want to retry some business processing every time it happens.

The classic example of this is the remote service call.

Spring Retry provides an AOP interceptor that wraps a method call in a RetryOperations instance for exactly this purpose.

The RetryOperationsInterceptor executes the intercepted method and retries on failure according to the RetryPolicy in the provided RepeatTemplate.

Spring Retry 提供了AOP interceptor ,interceptor执行被拦截的方法,尝试重试 依据 RepeatTemplate的RetryPolicy;

 

Java Configuration for Retry Proxies

You can add the @EnableRetry annotation to one of your @Configuration classes and use @Retryable on the methods (or on the type level for all methods) that you want to retry.

You can also specify any number of retry listeners.

The following example shows how to do so:

@Configuration的类 使用 @EnableRetry,且 在想被retry的方法使用 @Retryable

也可以指定多个retry listeners

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
@EnableRetry
public class Application {
 
    @Bean
    public Service service() {
        return new Service();
    }
 
    @Bean public RetryListener retryListener1() {
        return new RetryListener() {...}
    }
 
    @Bean public RetryListener retryListener2() {
        return new RetryListener() {...}
    }
 
}
 
@Service
class Service {
    @Retryable(RemoteAccessException.class)
    public service() {
        // ... do something
    }
}

  

You can use the attributes of @Retryable to control the RetryPolicy and BackoffPolicy, as follows:

通过 @Retryable 的属性 控制RetryPolicy、BackoffPolicy

1
2
3
4
5
6
7
@Service
class Service {
    @Retryable(maxAttempts=12, backoff=@Backoff(delay=100, maxDelay=500))
    public service() {
        // ... do something
    }
}

  

The preceding example creates a random backoff between 100 and 500 milliseconds and up to 12 attempts.

There is also a stateful attribute (default: false) to control whether the retry is stateful or not.

To use stateful retry, the intercepted method has to have arguments, since they are used to construct the cache key for the state.

 

The @EnableRetry annotation also looks for beans of type Sleeper and other strategies used in the RetryTemplate and interceptors to control the behavior of the retry at runtime.

The @EnableRetry annotation creates proxies for @Retryable beans, and the proxies (that is, the bean instances in the application) have the Retryable interface added to them.

This is purely a marker interface, but it might be useful for other tools looking to apply retry advice (they should usually not bother if the bean already implements Retryable).

 

If you want to take an alternative code path when the retry is exhausted, you can supply a recovery method.

Methods should be declared in the same class as the @Retryable instance and marked @Recover.

The return type must match the @Retryable method.

The arguments for the recovery method can optionally include the exception that was thrown and (optionally) the arguments passed to the original retryable method (or a partial list of them as long as none are omitted up to the last one needed).

The following example shows how to do so:

如果想在重试次数被耗尽后 执行其他逻辑,可以通过recovery方法;

@Recover 的方法的返回值类型 必须和 @Retryable的一致;

@Recover 的方法 允许一个异常 和 可选的参数;

1
2
3
4
5
6
7
8
9
10
11
@Service
class Service {
    @Retryable(retryFor = RemoteAccessException.class)
    public void service(String str1, String str2) {
        // ... do something
    }
    @Recover
    public void recover(RemoteAccessException e, String str1, String str2) {
       // ... error handling making use of original args if required
    }
}

  

To resolve conflicts between multiple methods that can be picked for recovery, you can explicitly specify recovery method name.

The following example shows how to do so:

为了解决 多个recovery方法冲突,可以通过指定方法名称;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
class Service {
    @Retryable(recover = "service1Recover", retryFor = RemoteAccessException.class)
    public void service1(String str1, String str2) {
        // ... do something
    }
 
    @Retryable(recover = "service2Recover", retryFor = RemoteAccessException.class)
    public void service2(String str1, String str2) {
        // ... do something
    }
 
    @Recover
    public void service1Recover(RemoteAccessException e, String str1, String str2) {
        // ... error handling making use of original args if required
    }
 
    @Recover
    public void service2Recover(RemoteAccessException e, String str1, String str2) {
        // ... error handling making use of original args if required
    }
}

  

 

 

Listeners

It is often useful to be able to receive additional callbacks for cross-cutting concerns across a number of different retries.

For this purpose, Spring Retry provides the RetryListener interface.

The RetryTemplate lets you register RetryListener instances, and they are given callbacks with the RetryContext and Throwable (where available during the iteration).

 

The following listing shows the RetryListener interface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface RetryListener {
 
    default <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
        return true;
    }
 
    default <T, E extends Throwable> void onSuccess(RetryContext context, RetryCallback<T, E> callback, T result) {
    }
 
    default <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
            Throwable throwable) {
    }
 
    default <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
            Throwable throwable) {
    }
 
}

  

The open and close callbacks come before and after the entire retry in the simplest case, and onSuccessonError apply to the individual RetryCallback calls; the current retry count can be obtained from the RetryContext.

The close method might also receive a Throwable.

Starting with version 2.0, the onSuccess method is called after a successful call to the callback.

This allows the listener to examine the result and throw an exception if the result doesn't match some expected criteria.

The type of the exception thrown is then used to determine whether the call should be retried or not, based on the retry policy.

If there has been an error, it is the last one thrown by the RetryCallback.

Note that when there is more than one listener, they are in a list, so there is an order.

In this case, open is called in the same order, while onSuccessonError, and close are called in reverse order.

 

 示例

1
2
3
4
5
<dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
            <version>1.3.0</version>
        </dependency>

  

编程式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class Config {
 
 
    @Bean
    public RetryTemplate retryTemplate(){
        return RetryTemplate.builder()
                .maxAttempts(3)
                .fixedBackoff(1000)
                .retryOn(Exception.class)
                .build();
    }
 
 
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class SpringRetryTest {
 
    @Autowired
    private RetryTemplate retryTemplate ;
 
    public void test() throws Throwable {
        retryTemplate.execute((RetryCallback<Object, Throwable>) context -> {
            int retryCount = context.getRetryCount();
            System.out.println("doWithRetry:" +retryCount);
            if (true){
                throw new TimeoutException();
            }
            return null;
        }, context -> {
            System.out.println("retry fail,recover");
            return null;
        });
    }
}

  

注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
@EnableRetry
public class Config {
 
}
 
 
@Service
public class SpringRetryTest {
 
    @Retryable(maxAttempts = 4, backoff = @Backoff(value = 1000), value = {Exception.class})
    public void test() throws Throwable {
        System.out.println("do retry...");
        if (true){
            throw new TimeoutException();
        }
    }
 
    @Recover
    public void recovery(Exception e, String s){
        System.out.println(e.getMessage());
        System.out.println("retry fail, do recovery...");
    }
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
public class Config {
 
    @Bean
    public RetryListener retryListener1() {
        return new RetryListener(){
 
            @Override
            public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
                System.out.println("open...");
                return true;
            }
 
            @Override
            public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
                System.out.println("close...");
            }
 
            @Override
            public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
                System.out.println("onError...");
            }
        };
    }
 
}

 

结果:  

open...
do retry...
onError...
do retry...
onError...
do retry...
onError...
do retry...
onError...
retry fail, do recovery...
close...

posted on   anpeiyong  阅读(34)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2022-04-11 C++概述1
2019-04-11 Maven系统学习

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示