1. 概述
Spring 重试提供了自动重新调用失败操作的功能。这在错误可能是暂时性的(如暂时的网络故障)的情况下很有帮助。
在本教程中,我们将看到使用 Spring 重试的各种方法:注释、RetryTemplate 和回调。
2. Maven 依赖项
让我们首先将 spring-retry 依赖项添加到我们的 pom.xml 文件中:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>2.0.0</version>
</dependency>
我们还需要在我们的项目中添加Spring AOP:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
查看 Maven Central 以获取最新版本的 spring-retry 和 spring-aspects 依赖项。
3. 启用弹簧重试
要在应用程序中启用 Spring 重试,我们需要将@EnableRetry注释添加到我们的 @Configuration 类中:
@Configuration
@EnableRetry
public class AppConfig { ... }
4. 使用弹簧重试
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的行为
为了自定义重试的行为,我们可以使用参数 maxTry 和退避:
@Service
public interface MyService {
@Retryable(retryFor = SQLException.class, maxAttempts = 2, backoff = @Backoff(delay = 100))
void retryServiceWithCustomization(String sql) throws SQLException;
}
最多会有两次尝试和 100 毫秒的延迟。
4.4. 使用弹簧属性
我们还可以在@Retryable注释中使用属性。
为了演示这一点,我们将了解如何将延迟和最大尝试次数的值外部化到属性文件中。
首先,让我们在一个名为 retryConfig 的文件中定义属性。属性:
retry.maxAttempts=2
retry.maxDelay=100
然后,我们指示我们的@Configuration类加载此文件:
// ...
@PropertySource("classpath:retryConfig.properties")
public class AppConfig { ... }
最后,我们可以在@Retryable定义中注入 retry.maxTry 和 retry.maxDelay 的值:
@Service
public interface MyService {
@Retryable(retryFor = SQLException.class, maxAttemptsExpression = "${retry.maxAttempts}",
backoff = @Backoff(delayExpression = "${retry.maxDelay}"))
void retryServiceWithExternalConfiguration(String sql) throws SQLException;
}
请注意,我们现在使用 maxTrysExpression 和 delayExpression,而不是 maxTry 和 delay。
5. 重试模板
5.1. 重试操作
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 是 RetryOperations的实现。
让我们在@Configuration类中配置一个 RetryTemplate bean:
@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;
}
}
重试策略确定何时应重试操作。
简单重试策略用于重试固定次数。另一方面,BackOffPolicy 用于控制重试尝试之间的退避。
最后,FixedBackOffPolicy 在继续之前会暂停固定的时间段。
5.3. 使用重试模板
要使用重试处理运行代码,我们可以调用 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);
}
}
打开和关闭回调在整个重试之前和之后出现,而 onError 应用于单个 RetryCallback 调用。
6.2. 注册监听器
接下来,我们将监听器(DefaultListenerSupport)注册到我们的RetryTemplate bean:
@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 重试。
这些示例的源代码可在 GitHub 上找到。