Spring 重试机制之listeners参数
Spring 中的重试功能中的listeners
参数能为我们实际项目做哪些事情?
# 背景
上篇文章中我们简单介绍了spring-retry
的功能及简单用法,但是注解@Retryable
还有一个参数listeners
我们没有进行说明,
那么本篇文章我们详细介绍一个这个参数的用法。
# 分析
由参数名字我们我们可以知道,这里面可以配置一些监听器。那这些监听器该如何进行配置呢?首先我们分析源码。
# 注解源码
我们只保留这个参数的源码,其他的省略掉了。
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {
/**
* Bean names of retry listeners to use instead of default ones defined in Spring
* context
* @return retry listeners bean names
*/
String[] listeners() default {};
}
2
3
4
5
6
7
8
9
10
11
说明:源码中该参数的解释:使用的重试监听器的
Bean
名称,而不是在Spring
上下文中定义的默认名称,我们可以大胆的猜测出 这个是一个特定的Bean
,需要开发者自己定义。并且可以接收的参数是数组形式,那么问题是如何定义呢?
# 处理注解源码
我们上篇文章中提到,处理注解 @Retryable
的类为:AnnotationAwareRetryOperationsInterceptor
,那么我们就在这个类中找寻我们要的答案。
- 方法
getListenersBeans()
# 源码
private RetryListener[] getListenersBeans(String[] listenersBeanNames) {
RetryListener[] listeners = new RetryListener[listenersBeanNames.length];
for (int i = 0; i < listeners.length; i++) {
listeners[i] = this.beanFactory.getBean(listenersBeanNames[i], RetryListener.class);
}
return listeners;
}
2
3
4
5
6
7
有上面的代码我们可以知道,我们自己定义的监听器肯定和
RetryListener
有着某种关系。下面我们分析该类的源码
RetryListener
# 源码
/**
* Interface for listener that can be used to add behaviour to a retry. Implementations of
* {@link RetryOperations} can chose to issue callbacks to an interceptor during the retry
* lifecycle.
*/
public interface RetryListener {
/**
* Called before the first attempt in a retry. For instance, implementers can set up
* state that is needed by the policies in the {@link RetryOperations}. The whole
* retry can be vetoed by returning false from this method, in which case a
* {@link TerminatedRetryException} will be thrown.
* @param <T> the type of object returned by the callback
* @param <E> the type of exception it declares may be thrown
* @param context the current {@link RetryContext}.
* @param callback the current {@link RetryCallback}.
* @return true if the retry should proceed.
*/
<T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback);
/**
* Called after the final attempt (successful or not). Allow the interceptor to clean
* up any resource it is holding before control returns to the retry caller.
* @param context the current {@link RetryContext}.
* @param callback the current {@link RetryCallback}.
* @param throwable the last exception that was thrown by the callback.
* @param <E> the exception type
* @param <T> the return value
*/
<T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);
/**
* Called after every unsuccessful attempt at a retry.
* @param context the current {@link RetryContext}.
* @param callback the current {@link RetryCallback}.
* @param throwable the last exception that was thrown by the callback.
* @param <T> the return value
* @param <E> the exception to throw
*/
<T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
分析:首先这个是一个接口,那不用想了,开发者自定义的监听器,必然要实现这个接口。
# 实现
由于listeners
这个参数可以是多个,并且入参是一个数组,我们先来定义两哥监听器。代码如下
RetryListenerBean
@Slf4j
public class RetryListenerBean implements RetryListener {
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
log.info("执行了 open 方法 ");
return true;
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
log.info("执行了 onError 方法,说明出现了异常");
}
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
log.info("执行了 close 方法 ");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
RetryListenerTwoBean
@Slf4j
public class RetryListenerTwoBean implements RetryListener {
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
log.info("执行了 第二个 open 方法 ");
return true;
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
log.info("执行了 第二个 onError 方法,说明出现了异常");
}
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
log.info("执行了 第二个 close 方法 ");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
分析1:我们定义了自己的两个监听器,在参数
listeners
进行配置,代码如下:
@Retryable(value = Exception.class,maxAttempts = 3,backoff = @Backoff(delay = 2000,multiplier = 1.5),listeners = {"retryListenerBean", "retryListenerTwoBean"} )
分析2:那么框架是如何识别到我们配置的监听器呢?请看源码
for (int i = 0; i < listeners.length; i++) {
listeners[i] = this.beanFactory.getBean(listenersBeanNames[i], RetryListener.class);
}
2
3
分析3:在上面的代码中,我们发现了
beanFactory#getBean()
,那么一切真相大白。因此我们必须将自己定义的监听器 交由Spring
进行管理。所以我们需要将自己定义的监听器进行配置。
# 监听器配置
我们将自己定义的监听器进行配置,由Spring
进行管理,配置代码如下:
@Configuration
public class Config {
@Bean("retryListenerBean")
public RetryListenerBean listenerBean(){
return new RetryListenerBean();
}
@Bean("retryListenerTwoBean")
public RetryListenerTwoBean listenerTwoBean(){
return new RetryListenerTwoBean();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
进行如上代码配置后,就可以通过
Bean
的名称通过getBean
的方法进行获取监听器的实例了。
# 测试
启动项目,我们使用postman
进行测试。日志如下:
2023-07-23 14:37:07.102 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.controller.RetryController : Controller 请求入参为:222
2023-07-23 14:37:25.312 INFO 12932 --- [nio-8111-exec-9] o.t.s.loopretry.bean.RetryListenerBean : 执行了 open 方法
2023-07-23 14:37:25.313 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.bean.RetryListenerTwoBean : 执行了 第二个 open 方法
2023-07-23 14:37:30.816 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.service.impl.RetryServiceImpl : Service 请求入参为:222
2023-07-23 14:37:31.285 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.service.impl.RetryServiceImpl : 进入测试方法,目前时间为:Sun Jul 23 14:37:31 CST 2023
2023-07-23 14:37:38.312 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.bean.RetryListenerTwoBean : 执行了 第二个 onError 方法,说明出现了异常
2023-07-23 14:37:38.313 INFO 12932 --- [nio-8111-exec-9] o.t.s.loopretry.bean.RetryListenerBean : 执行了 onError 方法,说明出现了异常
2023-07-23 14:37:54.729 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.service.impl.RetryServiceImpl : Service 请求入参为:222
2023-07-23 14:37:54.729 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.service.impl.RetryServiceImpl : 进入测试方法,目前时间为:Sun Jul 23 14:37:54 CST 2023
2023-07-23 14:37:54.729 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.bean.RetryListenerTwoBean : 执行了 第二个 onError 方法,说明出现了异常
2023-07-23 14:37:54.729 INFO 12932 --- [nio-8111-exec-9] o.t.s.loopretry.bean.RetryListenerBean : 执行了 onError 方法,说明出现了异常
2023-07-23 14:38:02.498 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.service.impl.RetryServiceImpl : Service 请求入参为:222
2023-07-23 14:38:02.499 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.service.impl.RetryServiceImpl : 进入测试方法,目前时间为:Sun Jul 23 14:38:02 CST 2023
2023-07-23 14:38:02.499 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.bean.RetryListenerTwoBean : 执行了 第二个 onError 方法,说明出现了异常
2023-07-23 14:38:02.499 INFO 12932 --- [nio-8111-exec-9] o.t.s.loopretry.bean.RetryListenerBean : 执行了 onError 方法,说明出现了异常
2023-07-23 14:38:02.500 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.service.impl.RetryServiceImpl : 异常出现后的回调操作,入参为:222,当前时间为:Sun Jul 23 14:38:02 CST 2023
2023-07-23 14:38:02.501 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.bean.RetryListenerTwoBean : 执行了 第二个 close 方法
2023-07-23 14:38:02.502 INFO 12932 --- [nio-8111-exec-9] o.t.s.loopretry.bean.RetryListenerBean : 执行了 close 方法
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
以上的日志,可以看出来我们定义的监听器中的日志输出,监听器的逻辑进行了执行。
# 小结
由监听器的代码,其中包括三个方案open
、onError
和close
我们结合日志的输出顺序,首先在执行我们的业务逻辑之前,先执行
open
方案,相当于一个前置拦截器,我们可以在这个方法中实现一些前置的逻辑操作。遇到异常的情况会执行onError
方法。最终会
执行close
方法。因此我们在不同的阶段利用这三个方法可以实现我们的想要的业务逻辑。
希望本篇文章能对大家理解这个参数有所帮助,若想看源码的小伙伴可以点击:代码传送门