java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > spring-retry之listeners参数

Spring中的接口重试机制spring-retry之listeners参数解析

作者:北漂码农有话说

这篇文章主要介绍了Spring中的接口重试机制spring-retry之listeners参数解析,注解@Retryable有一个参数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 {};
}

说明:源码中该参数的解释:使用的重试监听器的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;
}

有上面的代码我们可以知道,我们自己定义的监听器肯定和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);
}

分析:首先这个是一个接口,那不用想了,开发者自定义的监听器,必然要实现这个接口。

实现

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

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:我们定义了自己的两个监听器,在参数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);
}

分析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();
    }

}

进行如上代码配置后,就可以通过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 方法 

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

小结

由监听器的代码,其中包括三个方案open、onError和close我们结合日志的输出顺序。

首先在执行我们的业务逻辑之前,先执行 open方案,相当于一个前置拦截器,我们可以在这个方法中实现一些前置的逻辑操作。

遇到异常的情况会执行onError方法。最终会 执行close方法。因此我们在不同的阶段利用这三个方法可以实现我们的想要的业务逻辑。

到此这篇关于Spring中的接口重试机制spring-retry之listeners参数解析的文章就介绍到这了,更多相关spring-retry之listeners参数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文