Spring Retry 教程
1、概览
Spring Retry 提供了自动重新调用失败操作的能力。这在错误可能是短暂的(例如瞬时的网络故障)情况下非常有帮助。
本文将带你了解使用 Spring Retry 的各种方法:注解、RetryTemplate
和回调。
2、Maven 依赖
首先,在 pom.xml
文件中添加 spring-retry
以及 Spring AOP 依赖:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.11</version>
</dependency>
你可以在 Maven Central 获取 spring-retry 和 spring-aspects 依赖的最新版本。
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);
}
@Recover
注解定义了当 @Retryable
方法因指定异常而失败时的恢复方法。
因此,如果 retryServiceWithRecovery
方法在尝试三次后仍抛出 SqlException
,就会调用 recover()
方法。
Recovery Handler 方法的第一个参数应为 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;
}
如上,最多可尝试 2 次,延迟时间为 100 毫秒。
4.4、使用 Spring Properties
还可以在 @Retryable
注解中使用 Properties。
首先,在名为 retryConfig.properties
的文件中定义属性:
retry.maxAttempts=2 # 最大尝试次数
retry.maxDelay=100 # 延迟
然后,通过 @Configuration
类加载该文件:
// ...
@PropertySource("classpath:retryConfig.properties")
public class AppConfig { ... }
最后,在 @Retryable
注入 Properties 文件中定义的 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;
...
}
execute()
中的 RetryCallback<T>
(重试回调)参数,它是一个接口,允许插入需要在失败时重试的业务逻辑:
public interface RetryCallback<T> {
T doWithRetry(RetryContext context) throws Throwable;
}
5.2、 RetryTemplate 配置
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;
}
}
RetryPolicy
决定何时重试操作。
SimpleRetryPolicy
用于固定次数的重试,而 BackOffPolicy
用于控制重试尝试之间的回退(backoff)。
最后,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、Listener
Listener(监听器)会在重试时提供额外的回调,可以利用这些回调处理不同重试中的各种横切关注点(cross-cutting concerns)。
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、注册 Listener
接下来,向 RetryTemplate
Bean 注册监听器(DefaultListenerSupport
):
@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 和回调 Listener(监听器)来使用 Spring Retry。
Ref:https://www.baeldung.com/spring-retry