Listeners parameter of Spring interface retry mechanism

Background

In the previous article, we briefly introduced the function and simple usage of spring-retry, but the annotation @Retryable also has a parameter listeners that we did not carry out. illustrate,
So in this article we introduce the usage of this parameter in detail.

Analysis

We can know from the parameter name that some listeners can be configured here. So how to configure these listeners? First we analyze the source code.

Annotation source code

We only keep the source code of this parameter and omit the others.

@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 {<!-- -->};
}

Note: The explanation of this parameter in the source code: the Bean name of the retry listener is used instead of the default name defined in the Spring context. We can boldly guess
This is a specific Bean that needs to be defined by the developer. And the parameters that can be received are in the form of arrays, so the question is how to define them?

Processing annotation source code

As mentioned in our last article, the class that handles the annotation @Retryable is: AnnotationAwareRetryOperationsInterceptor, so we will find the answer we want in this class.

  • MethodgetListenersBeans()

Source code

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;
}

From the above code, we can know that the listener we defined must have some relationship with RetryListener. Next we analyze the source code of this class

  • RetryListener

Source code

/**
 * Interface for listener that can be used to add behavior 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 vetted 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);
}

Analysis: First of all, this is an interface, so don’t think about it. Developer-defined listeners must implement this interface.

Implementation

Since the parameter listeners can be multiple, and the input parameter is an array, we first define the listener for the two brothers. code show as below

  • RetryListenerBean
@Slf4j
public class RetryListenerBean implements RetryListener {<!-- -->

    @Override
    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {<!-- -->
        log.info("The open method was executed");
        return true;
    }

    @Override
    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {<!-- -->
        log.info("The onError method was executed, indicating that an exception occurred");
    }

    @Override
    public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {<!-- -->
        log.info("The close method was executed");
    }
}

  • RetryListenerTwoBean
@Slf4j
public class RetryListenerTwoBean implements RetryListener {<!-- -->

    @Override
    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {<!-- -->
        log.info("The second open method was executed");
        return true;
    }

    @Override
    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {<!-- -->
        log.info("The second onError method was executed, indicating that an exception occurred");
    }

    @Override
    public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {<!-- -->
        log.info("The second close method was executed");
    }
}

Analysis 1: We defined our own two listeners and configured them in the parameter listeners. The code is as follows:

@Retryable(value = Exception.class,maxAttempts = 3,backoff = @Backoff(delay = 2000,multiplier = 1.5),listeners = {<!-- -->"retryListenerBean", "retryListenerTwoBean\ "} )

Analysis 2: So how does the framework identify the listener we configured? Please see the source code

for (int i = 0; i < listeners.length; i + + ) {<!-- -->
    listeners[i] = this.beanFactory.getBean(listenersBeanNames[i], RetryListener.class);
}

Analysis 3: In the above code, we found beanFactory#getBean(), then all the truth is revealed. Therefore we must define our own listener
It is managed by Spring. So we need to configure the listener we defined.

Listener configuration

We configure the listener we defined and it is managed by Spring. The configuration code is as follows:

@Configuration
public class Config {<!-- -->

    @Bean("retryListenerBean")
    public RetryListenerBean listenerBean(){<!-- -->
        return new RetryListenerBean();
    }

    @Bean("retryListenerTwoBean")
    public RetryListenerTwoBean listenerTwoBean(){<!-- -->
        return new RetryListenerTwoBean();
    }

}

After configuring the above code, you can obtain the instance of the listener through the name of Bean and the method of getBean.

Test

Start the project and we use postman for testing. The log is as follows:

2023-07-23 14:37:07.102 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.controller.RetryController: Controller request input parameter is: 222
2023-07-23 14:37:25.312 INFO 12932 --- [nio-8111-exec-9] o.t.s.loopretry.bean.RetryListenerBean: The open method was executed
2023-07-23 14:37:25.313 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.bean.RetryListenerTwoBean: The second open method was executed
2023-07-23 14:37:30.816 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.service.impl.RetryServiceImpl: Service request input parameter is: 222
2023-07-23 14:37:31.285 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.service.impl.RetryServiceImpl: Enter the test method, the current time is: 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: The second onError method was executed, indicating that an exception occurred
2023-07-23 14:37:38.313 INFO 12932 --- [nio-8111-exec-9] o.t.s.loopretry.bean.RetryListenerBean: The onError method was executed, indicating that an exception occurred
2023-07-23 14:37:54.729 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.service.impl.RetryServiceImpl: Service request input parameters are: 222
2023-07-23 14:37:54.729 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.service.impl.RetryServiceImpl: Enter the test method, the current time is: 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: The second onError method was executed, indicating that an exception occurred
2023-07-23 14:37:54.729 INFO 12932 --- [nio-8111-exec-9] o.t.s.loopretry.bean.RetryListenerBean: The onError method was executed, indicating that an exception occurred
2023-07-23 14:38:02.498 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.service.impl.RetryServiceImpl: Service request input parameters are: 222
2023-07-23 14:38:02.499 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.service.impl.RetryServiceImpl: Enter the test method, the current time is: 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: The second onError method was executed, indicating that an exception occurred
2023-07-23 14:38:02.499 INFO 12932 --- [nio-8111-exec-9] o.t.s.loopretry.bean.RetryListenerBean: The onError method was executed, indicating that an exception occurred
2023-07-23 14:38:02.500 INFO 12932 --- [nio-8111-exec-9] o.t.s.l.service.impl.RetryServiceImpl: callback operation after an exception occurs, the input parameter is: 222, the current time is: 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: The second close method was executed
2023-07-23 14:38:02.502 INFO 12932 --- [nio-8111-exec-9] o.t.s.loopretry.bean.RetryListenerBean: executed the close method

From the above log, we can see that the log output in the listener we defined is executed, and the logic of the listener is executed.

Summary

From the listener code, which includes three programs open, onError and close, we combine the output sequence of the log, first executing our business Before logic, execute
The open solution is equivalent to a front-end interceptor. We can implement some front-end logical operations in this method. When encountering an abnormal situation, the onError method will be executed. will eventually
Execute the close method. Therefore, we can use these three methods at different stages to achieve our desired business logic.

I hope this article can help everyone understand this parameter. If you want to see the source code, you can click: Code Portal