该项目为Spring应用程序提供声明式重试支持,它用于SpringBatch、SpringIntegration、ApacheHadoop的Spring(以及其他),命令式重试也支持显式使用。
入门
声明式示例
Configuration
EnableRetrypublicclassApplication{BeanpublicServiceservice(){returnnewService();}}ServiceclassService{Retryable(RemoteAccessException.class)publicvoidservice(){//...dosomething}Recoverpublicvoidrecover(RemoteAccessExceptione){//...panic}}调用service方法,如果它由于RemoteAccessException失败,那么它将重试(默认情况下最多三次),如果继续失败,则执行recover方法,
Retryable注解属性中有各种选项,用于包含和排除异常类型、限制重试次数和回退策略。使用上面显示的
Retryable注解应用重试处理的声明式方法对AOP类有一个额外的运行时依赖,有关如何解决项目中的这种依赖关系的详细信息,请参阅下面的“重试代理的Java配置”部分。命令式示例
RetryTemplatetemplate=RetryTemplate.builder().maxAttempts(3).fixedBackoff().retryOn(RemoteAccessException.class).build();template.execute(ctx-{//...dosomething});
旧版本:参见RetryTemplate部分中的示例。
构建
要求Java1.7和Maven3.0.5(或更高)。
mvninstall
特性和API
RetryTemplate
为了使处理更健壮、更不容易失败,有时自动重试失败的操作会有所帮助,以防它在随后的尝试中可能成功,易受这种处理影响的错误本质上是暂时的。例如,对web服务或RMI服务的远程调用由于网络故障或数据库更新中的DeadLockLoserException而失败,可能在短时间的等待后自行解决,要自动化这些操作的重试,SpringRetry具有RetryOperations策略,RetryOperations接口看起来是这样的:
publicinterfaceRetryOperations{TTexecute(RetryCallbackTretryCallback)throwsException;TTexecute(RetryCallbackTretryCallback,RecoveryCallbackTrecoveryCallback)throwsException;TTexecute(RetryCallbackTretryCallback,RetryStateretryState)throwsException,ExhaustedRetryException;TTexecute(RetryCallbackTretryCallback,RecoveryCallbackTrecoveryCallback,RetryStateretryState)throwsException;}
基本回调是一个简单的接口,允许你插入一些要重试的业务逻辑:
publicinterfaceRetryCallbackT{TdoWithRetry(RetryContextcontext)throwsThrowable;}
执行回调,如果它失败(通过抛出Exception),将重试它,直到成功或实现决定中止为止。RetryOperations接口中有许多重载的execute方法,它们处理各种用例,以便在所有重试尝试都耗尽时进行恢复,还有重试状态,这允许客户端和实现在调用之间存储信息(稍后将详细介绍)。
RetryOperations最简单的通用实现是RetryTemplate,它可以这样用:
RetryTemplatetemplate=newRetryTemplate();TimeoutRetryPolicypolicy=newTimeoutRetryPolicy();policy.setTimeout(L);template.setRetryPolicy(policy);Fooresult=template.execute(newRetryCallbackFoo(){publicFoodoWithRetry(RetryContextcontext){//Dostuffthatmightfail,e.g.webserviceoperationreturnresult;}});
在本例中,我们执行一个web服务调用并将结果返回给用户,如果该调用失败,则重试该调用,直到达到超时为止。
从1.3版开始,RetryTemplate的流畅配置也可用:
RetryTemplate.builder().maxAttempts(10).exponentialBackoff(,2,0).retryOn(IOException.class).traversingCauses().build();RetryTemplate.builder().fixedBackoff(10).withinMillis().build();RetryTemplate.builder().infiniteRetry().retryOn(IOException.class).uniformRandomBackoff(,).build();
RetryContext
RetryCallback的方法参数是一个RetryContext,许多回调将简单地忽略上下文,但是如果需要,它可以作为一个属性包来存储迭代期间的数据。
如果同一个线程中正在进行嵌套重试,则RetryContext将具有父上下文,父上下文有时对于存储需要在执行的调用之间共享的数据很有用。
RecoveryCallback
当重试耗尽时,RetryOperations可以将控制权传递给另一个回调RecoveryCallback,要使用此功能,客户端只需将回调函数一起传递给相同的方法,例如:
Foofoo=template.execute(newRetryCallbackFoo(){publicFoodoWithRetry(RetryContextcontext){//businesslogichere},newRecoveryCallbackFoo(){Foorecover(RetryContextcontext)throwsException{//recoverlogichere}});
如果在模板决定中止之前业务逻辑没有成功,那么客户端就有机会通过恢复回调执行一些替代处理。
无状态重试
在最简单的情况下,重试只是一个while循环,RetryTemplate可以一直尝试,直到成功或失败。RetryContext包含一些状态来决定是重试还是中止,但是这个状态位于堆栈上,不需要将它存储在全局的任何位置,因此我们将此称为无状态重试。无状态重试和有状态重试之间的区别包含在RetryPolicy的实现中(RetryTemplate可以同时处理这两种情况),在无状态重试中,回调总是在重试失败时在同一个线程中执行。
有状态重试
如果失败导致事务性资源无效,则需要特别考虑,这并不适用于简单的远程调用,因为(通常)没有事务资源,但有时确实适用于数据库更新,尤其是在使用Hibernate时。在这种情况下,只有立即重新抛出调用失败的异常才有意义,以便事务可以回滚并启动一个新的有效的事务。
在这些情况下,无状态重试是不够的,因为重新抛出和回滚必然会离开RetryOperations.execute()方法,并可能丢失堆栈上的上下文。为了避免丢失它,我们必须引入一种存储策略,将它从堆栈中取出并(至少)放入堆存储中,为此,SpringRetry提供了一种存储策略RetryContextCache,可以将其注入RetryTemplate。RetryContextCache的默认实现在内存中,使用一个简单的Map,它有一个严格执行的最大容量,以避免内存泄漏,但它没有任何高级缓存功能,如生存时间。如果需要,应该考虑注入具有这些特性的Map,在集群环境中对多个进程的高级使用可能还会考虑使用某种集群缓存实现RetryContextCache(不过,即使在集群环境中,这也可能是多余的)。
RetryOperations的部分职责是在失败的操作在新执行中返回时识别它们(通常封装在新事务中),为了促进这一点,SpringRetry提供了RetryState抽象,这与RetryOperations中的特殊execute方法一起工作。
识别失败操作的方法是跨重试的多个调用标识状态,要标识状态,用户可以提供RetryState对象,该对象负责返回标识该项的唯一键,标识符用作RetryContextCache中的键。
在RetryState返回的键中实现Object.equals()和Object.hashCode()要非常小心,最好的建议是使用业务键来标识项,对于JMS消息,可以使用消息ID。
当重试耗尽时,还可以选择以另一种方式处理失败的项,而不是调用RetryCallback(现在假定很可能会失败),就像在无状态的情况下一样,这个选项是由RecoveryCallback提供的,它可以通过将其传递给RetryOperations的execute方法来提供。
重试或不重试的决定实际上委托给了一个常规的RetryPolicy,因此可以在那里注入对限制和超时的常见