思考集结处

vuePress-theme-reco 思考集结处    2024
思考集结处 思考集结处

Choose mode

  • dark
  • auto
  • light
首页
标签
分类
  • AI
  • Docker
  • 分布式事务
  • 文件存储
  • 框架
  • Spring
  • java
  • 其他
  • 搜索引擎
  • 源码
  • 网站
Java
网站
容器技术
搜索引擎
分布式事务
源码系列
框架系列
文件存储
AI
其他
GitHub
author-avatar

思考集结处

43

文章

18

标签

首页
标签
分类
  • AI
  • Docker
  • 分布式事务
  • 文件存储
  • 框架
  • Spring
  • java
  • 其他
  • 搜索引擎
  • 源码
  • 网站
Java
网站
容器技术
搜索引擎
分布式事务
源码系列
框架系列
文件存储
AI
其他
GitHub
  • Java相关技术
  • 网络编程相关概念
  • 同步阻塞IO模型
  • 同步非阻塞IO模型
  • 多路复用IO模型
  • 异步IO模型
  • 多线程源码分析
  • 线程池
  • Spring 事件监听
  • Spring 重试机制
  • Spring 重试机制之listeners参数

Spring 重试机制之listeners参数

vuePress-theme-reco 思考集结处    2024

Spring 重试机制之listeners参数

思考集结处 2023-07-23 重试机制之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 {};
}
1
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;
}
1
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);
}
1
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 方法 ");
    }
}

1
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 方法 ");
    }
}
1
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"} )
1

分析2:那么框架是如何识别到我们配置的监听器呢?请看源码

for (int i = 0; i < listeners.length; i++) {
    listeners[i] = this.beanFactory.getBean(listenersBeanNames[i], RetryListener.class);
}
1
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();
    }

}
1
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 方法 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

以上的日志,可以看出来我们定义的监听器中的日志输出,监听器的逻辑进行了执行。

# 小结

由监听器的代码,其中包括三个方案open、onError和close我们结合日志的输出顺序,首先在执行我们的业务逻辑之前,先执行 open方案,相当于一个前置拦截器,我们可以在这个方法中实现一些前置的逻辑操作。遇到异常的情况会执行onError方法。最终会 执行close方法。因此我们在不同的阶段利用这三个方法可以实现我们的想要的业务逻辑。

希望本篇文章能对大家理解这个参数有所帮助,若想看源码的小伙伴可以点击:代码传送门

我是思考集结处欢迎你的关注
看板娘