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.
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.
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.
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;
A 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 onSuccess
, onError
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 onSuccess
, onError
, 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...
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
2022-04-11 C++概述1
2019-04-11 Maven系统学习