一、项目的配置
为了启用 Spring Retry 的支持,首先要在
pom.xml
文件中添加以下依赖项
<properties>
<version.spring.retry>1.3.1</version.spring.retry>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>${version.spring.retry}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
</dependencies>
在这里我们使用的是maven中央仓库中最新的制品
1.3.1
。除了其本身的依赖,正常使用Spring Retry
还需要 依赖AOP
。对于Spring Boot
项目,在pom.xml
中添加Spring-Boot-starter-aop starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
二、启用Spring Retry支持
为了启用Spring Retry的功能,需要向配置类添加@EnableRetry
注释。
@SpringBootApplication
@EnableRetry
public class Launcher {
public static void main(String[] args) {
SpringApplication.run(Launcher.class, args);
}
}
使用Retry的功能,有两种常见的方式,包括@Retryable
注解到方法,以及RetryTemplate
配置策略后手动去执行。
三、@Retryable 注解形式
3.1 @Retryable
注解方式就是在启用重试特性的方法上使用@Retryable
注释。
我们提供一个接口,提供一个需要重试的方法,并为这个方式赋于改注解。
public interface RetryService {
/**
* 指定异常CustomRetryException重试,重试最大次数为4(默认是3),重试补偿机制间隔200毫秒
* 还可以配置exclude,指定异常不充实,默认为空
* @return result
* @throws CustomRetryException 指定异常
*/
@Retryable(value = {CustomRetryException.class},maxAttempts = 4,backoff = @Backoff(200))
String retry() throws CustomRetryException;
}
注解参数的赋值内容解释见方法的注释。
-
value属性告诉 Spring retry 在方法在
CustomRetryException
异常出现时触发重试。 -
maxAttempts设置重试的最大次数,如果没有指定默认值为3。
-
backoff指定下次重试的延迟时间,默认值为1秒。
CustomRetryException
异常是自定义的异常,代码如下
public class CustomRetryException extends Exception{
public CustomRetryException(String error){
super(error);
}
}
3.2 @Recover
当被@Retryable
注解的方法由于指定的异常而失败时,用于定义单独恢复方法的@Recover
注释。
@Service
@Slf4j
public class RetryServiceImpl implements RetryService {
private static int count = 1;
@Override
public String retry() throws CustomRetryException {
log.info("retry{},throw CustomRetryException in method retry",count);
count ++;
throw new CustomRetryException("throw custom exception");
}
@Recover
public String recover(Throwable throwable) {
log.info("Default Retry service test");
return "Error Class :: " + throwable.getClass().getName();
}
}
3.3 测试
通过Junit
进行单元测试。
@Test
void retry() {
try {
final String message = retryService.retry();
log.info("message = "+message);
} catch (CustomRetryException e) {
log.error("Error while executing test {}",e.getMessage());
}
}
测试输出结果如下,符合预期,四次执行retry
方法,最后一次执行结束后进入recover
方法。
a77e9e674a3fb25288b99a45a377d43.png
四、RetryTemplate
RetryTemplate
是使用注解形式重试的一种替代。
4.1 RetryOperations
使用 RetryOperations
接口的 Spring retry providesRetryOperations
策略。
public interface RetryOperations {
<T> T execute(RetryCallback < T > retryCallback) throws Exception;
// other execute methods
<T> T execute(RetryCallback < T > retryCallback, RecoveryCallback < T > recoveryCallback,
RetryState retryState) throws Exception;
}
RetryCallback
允许插入在失败时需要重试的业务逻辑。
public interface RetryCallback <T> {
T doWithRetry(RetryContext context) throws Throwable;
}
4.2 RetryTemplate
RetryTemplate
提供了RetryOperations
的一种具体实现。它被认为是从中创建bean的良好做法。
@Bean
@ConditionalOnMissingBean
public RetryTemplate retryTemplate(){
final SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts(4);
final FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(1000L);
return RetryTemplate.builder()
.customPolicy(simpleRetryPolicy)
.customBackoff(fixedBackOffPolicy)
.retryOn(CustomRetryException.class)
.build();
}
进行单元测试。
@Autowired
pivate RetryTemplate retryTemplate;
@Test
void retryWithoutAnnotation(){
try {
String message = retryTemplate.execute(x -> retryService.retryWithoutAnnotation());
log.info("message = "+message);
} catch (CustomRetryException e) {
log.error("Error while executing test {}",e.getMessage());
}
}
如下图的输出结果所示,也是重试了四次,但是没有recover
的策略。
fc44465528aeaf661622a9712a2aa3e.png
4.2 RecoveryCallback
execute
时,可以选择输入RecoveryCallback
回调,确定重试结束后,仍然出现异常的recovery
行为。方法签名如下。
public final <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback) throws E {
return this.doExecute(retryCallback, recoveryCallback, (RetryState)null);
}
所以,我们先自定义RecoveryCallback
。
@Slf4j
public class CustomRecoveryCallback implements RecoveryCallback<String> {
@Override
public String recover(RetryContext retryContext) throws Exception {
log.info("Default Retry service test,total retry {}",retryContext.getRetryCount());
return "Error Class :: " + retryContext.getLastThrowable().getClass().getName();
}
}
然后进行单元测试。
@Autowired
private CustomRecoveryCallback customRecoveryCallback;
@Test
void retryWithoutAnnotation(){
try {
String message = retryTemplate.execute(x -> retryService.retryWithoutAnnotation(), customRecoveryCallback);
log.info("message = "+message);
} catch (CustomRetryException e) {
log.error("Error while executing test {}",e.getMessage());
}
}
如下图的输出结果所示,重试完成后,执行了我们自定义的recover
。
d5bfb79bc99998c1ba6ac3679df6c92.png
五、RetryListenerSupport
如果我们想在重试整个生命周期中,按照不同的阶段设置一些事件监听处理机制,那怎么办呢?设置自定义的RetryListenerSupport
能帮助到我们。我们继承RetryListenerSupport
,并重新Override
close
、onError
、open
方法,这三个方法分别表示
-
所有重试结束时
-
每一次重试发生异常时
-
重试正式开始前。
@Slf4j
public class DefaultListenerSupport extends RetryListenerSupport {
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
log.info("DefaultListenerSupport close");
super.close(context, callback, throwable);
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
log.info("DefaultListenerSupport onError");
super.onError(context, callback, throwable);
}
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
log.info("DefaultListenerSupport open");
return super.open(context, callback);
}
}
并且在构造RetryTemaplate
时候,设置withListener
字段。
@Bean
@ConditionalOnMissingBean
public RetryListenerSupport retryListenerSupport(){
return new DefaultListenerSupport();
}
@Bean
@ConditionalOnMissingBean
public RetryTemplate retryTemplate(RetryListenerSupport retryListenerSupport){
...
return RetryTemplate.builder()
.customPolicy(simpleRetryPolicy)
.customBackoff(fixedBackOffPolicy)
.withListener(retryListenerSupport)
.retryOn(CustomRetryException.class)
.build();
}
运行单元测试,输出结果如下。
六、总结
在这篇文章中,我们看到了 Spring retry
的不同特性,我们清楚使用它能使应用程序更加健壮。我们实践了Spring Retry
最为常见的几种用法,主要包括@Retryable
注释和 RetryTemplate
。
那么哪些地方我们能用到Spring Retry
呢?有这两点建议
-
仅在临时错误上使用重试。不建议它在永久错误中使用它,因为这样可能导致系统性能问题。
-
它不是熔断器的替代的一种方式,最好在允许的情况下,既使用熔断器,又使用重试器。