I am using @Retryable at various service methods. Some of them need some action to be done before the retry happens. I use a RetryListener implementation to achieve this. Well, that ended somehow weird.
onError()
is invoked though not configuredonError()
is invoked for exceptions not present at retryFor
issue 1.
Here is the listener code
package com.example.springbootretrytest.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.stereotype.Component;
@Component("testServiceRetryListener")
public class TestServiceRetryListener implements RetryListener {
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
// do something
}
}
My expectation is that the RetryListener bean like from above is only invoked if it is configured like this
@Retryable(listeners={"testServiceRetryListener"})
public String doSomething(String someParam) {
// do something
}
Instead now methods without any listeners
argument at their @Retryable
annotation lead to invoking each of the RetryListener beans I have in my application. I now have to provide each of the methods with a listeners={"defaultRetryListener"}
to prevent invoking those others.
Am I doing something wrong here? Is there a way to configure listener invocation behaviour on another level?
issue 2.
Given the following method
@Retryable(retryFor = TestReasonAException.class, listeners = {"testServiceRetryListener"})
public String doSomething(String someParam) {
// do something
}
The listener's onError()
is invoked also for other exceptions like TestReasonBException.class
(no inheritence). Compared with the issue 1. this is less severe. I could handle it in my onError()
implementation. Nevertheless, is this how it is intended to be?
Full sample code can be found at https://github.com/nullnullschmidt/spring-boot-retry-test.git
Both are "as designed".
First, any listener beans defined in the application context (such as your @Component
listener) are considered "global" listeners and applied to any/all @Retryable
methods.
The javadocs explain how setting listeners on the annotation disable the use of the global listeners for this method.
/**
* Bean names of retry listeners to use instead of default ones defined in Spring
* context
* @return retry listeners bean names
*/
Second, the listener invocations are at a much lower level; the onError()
method is called long before the decision as to whether the exception is retryable or not is made.
Not-retryable exceptions will also go to any configured @Recover
method (immediately, rather than after any retries).