1. 概述
Spring Retry 提供了自动重新调用失败操作的能力。这对于像暂时的网络故障这样的临时错误非常有帮助。
在这个教程中,我们将探讨使用 Spring Retry 的各种方式:注解、RetryTemplate
和回调。
2. Maven 依赖
首先,我们将在 pom.xml
文件中添加 spring-retry
的依赖:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>2.0.3</version>
</dependency>
同时,我们也需要在项目中添加 Spring AOP:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.1.5</version>
</dependency>
有关 spring-retry 和 spring-aspects 最新版本,请查看 Maven 中心仓库。
3. 启用 Spring Retry
要在应用程序中启用 Spring Retry,我们需要在 @Configuration
类上添加 @EnableRetry
注解:
@Configuration
@EnableRetry
public class AppConfig { ... }
4. 使用 Spring Retry
4.1. 使用带有恢复的 @Retryable
我们可以使用 @Retryable
注解为方法添加重试功能:
@Service
public interface MyService {
@Retryable
void retryService(String sql);
}
由于这里没有指定任何异常,对于所有抛出的异常都将尝试重试。如果达到最大尝试次数后仍然出现异常,将抛出 ExhaustedRetryException
。
按照 @Retryable
的默认行为,重试可能最多尝试三次,每次重试之间间隔一秒。
4.2. @Retryable
和 @Recover
现在让我们使用 @Recover
注解添加一个恢复方法:
@Service
public interface MyService {
@Retryable(retryFor = SQLException.class)
void retryServiceWithRecovery(String sql) throws SQLException;
@Recover
void recover(SQLException e, String sql);
}
当抛出 SQLException
时,将尝试重试。@Recover
注解定义了一个单独的恢复方法,当 @Retryable
方法因特定异常失败时调用。
因此,如果 retryServiceWithRecovery
方法在三次尝试后仍不断抛出 SQLException
,则会调用 recover()
方法。
恢复处理器应具有类型为 Throwable
(可选)的第一个参数和相同的返回类型。以下参数按失败方法的参数列表顺序从失败方法中填充。
4.3. 自定义 @Retryable
行为
为了自定义重试行为,我们可以使用参数 maxAttempts
和 backoff
:
@Service
public interface MyService {
@Retryable(retryFor = SQLException.class, maxAttempts = 2, backoff = @Backoff(delay = 100))
void retryServiceWithCustomization(String sql) throws SQLException;
}
将最多尝试两次,并且每次间隔100毫秒。
4.4. 使用 Spring 属性
我们还可以在 @Retryable
注解中使用属性。
为了演示这一点,我们将看到如何将 delay
和 max attempts
的值外部化到一个配置文件中。
首先,我们在名为 retryConfig.properties
的文件中定义属性:
retry.maxAttempts=2
retry.maxDelay=100
然后,指示我们的 @Configuration
类加载这个文件:
// ...
@PropertySource("classpath:retryConfig.properties")
public class AppConfig { ... }
最后,我们可以在 @Retryable
定义中注入 retry.maxAttempts
和 retry.maxDelay
的值:
@Service
public interface MyService {
@Retryable(retryFor = SQLException.class, maxAttemptsExpression = "${retry.maxAttempts}",
backoff = @Backoff(delayExpression = "${retry.maxDelay}"))
void retryServiceWithExternalConfiguration(String sql) throws SQLException;
}
请注意,我们现在使用 maxAttemptsExpression
和 delayExpression
而不是 maxAttempts
和 delay
。
5. RetryTemplate
5.1. RetryOperations
Spring Retry 提供了 RetryOperations
接口,它提供了一组 execute()
方法:
public interface RetryOperations {
<T> T execute(RetryCallback<T> retryCallback) throws Exception;
...
}
RetryCallback
是 execute()
方法的参数,它允许在操作失败时插入需要重试的业务逻辑:
public interface RetryCallback<T> {
T doWithRetry(RetryContext context) throws Throwable;
}
5.2. RetryTemplate
配置
RetryTemplate
是 RetryOperations
的实现。
让我们在 @Configuration
类中配置一个 RetryTemplate
对象:
@Configuration
public class AppConfig {
//...
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(2000l);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(2);
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
}
RetryPolicy
决定何时应该重试操作。
SimpleRetryPolicy
用于固定次数的重试,而 BackOffPolicy
用于控制重试之间的延退。
最后,FixedBackOffPolicy
在继续之前暂停固定的时间。
5.3. 使用 RetryTemplate
要运行带有重试处理的代码,我们可以调用 retryTemplate.execute()
方法:
retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
@Override
public Void doWithRetry(RetryContext arg0) {
myService.templateRetryService();
...
}
});
我们可以使用 lambda 表达式而不是匿名类:
retryTemplate.execute(arg0 -> {
myService.templateRetryService();
return null;
});
6. 监听器
监听器提供了额外的重试回调,可用于不同重试中的跨切关注点。
6.1. 添加回调
回调由 RetryListener
接口提供:
public class DefaultListenerSupport extends RetryListenerSupport {
@Override
public <T, E extends Throwable> void close(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
logger.info("onClose");
...
super.close(context, callback, throwable);
}
@Override
public <T, E extends Throwable> void onError(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
logger.info("onError");
...
super.onError(context, callback, throwable);
}
@Override
public <T, E extends Throwable> boolean open(RetryContext context,
RetryCallback<T, E> callback) {
logger.info("onOpen");
...
return super.open(context, callback);
}
}
open
和 close
回调分别在整个重试前后执行,而 onError
应用于每个 RetryCallback
调用。
6.2. 注册监听器
接下来,我们将我们的监听器 (DefaultListenerSupport
) 注册到 RetryTemplate
对象:
@Configuration
public class AppConfig {
...
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
...
retryTemplate.registerListener(new DefaultListenerSupport());
return retryTemplate;
}
}
7. 测试结果
为了完成示例,让我们验证结果:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes = AppConfig.class,
loader = AnnotationConfigContextLoader.class)
public class SpringRetryIntegrationTest {
@Autowired
private MyService myService;
@Autowired
private RetryTemplate retryTemplate;
@Test(expected = RuntimeException.class)
public void givenTemplateRetryService_whenCallWithException_thenRetry() {
retryTemplate.execute(arg0 -> {
myService.templateRetryService();
return null;
});
}
}
从测试日志中可以看出,我们已经正确配置了 RetryTemplate
和 RetryListener
:
2020-01-09 20:04:10 [main] INFO o.b.s.DefaultListenerSupport - onOpen
2020-01-09 20:04:10 [main] INFO o.baeldung.springretry.MyServiceImpl
- throw RuntimeException in method templateRetryService()
2020-01-09 20:04:10 [main] INFO o.b.s.DefaultListenerSupport - onError
2020-01-09 20:04:12 [main] INFO o.baeldung.springretry.MyServiceImpl
- throw RuntimeException in method templateRetryService()
2020-01-09 20:04:12 [main] INFO o.b.s.DefaultListenerSupport - onError
2020-01-09 20:04:12 [main] INFO o.b.s.DefaultListenerSupport - onClose
8. 总结
在这篇文章中,我们了解了如何使用注解、RetryTemplate
和回调监听器来使用 Spring Retry。
示例代码可在 GitHub 上 查看。