Spring Retry does not work on 2nd level of methods
So this is a super late answer, but since I've just come here and confronted the same problem (again, after years ago wrestling with transactions) I'll furnish a little more fleshed out solution and hopefully someone will find it useful. Suffice to say that @M. Deinum's diagnosis is correct.
In the above case, and to paraphrase Understanding AOP proxies, any place where SphIptvClient
gets autowired will be given a reference to a proxy which Spring Retry will create when @EnableRetry
is handled:
"The
@EnableRetry
annotation creates proxies for@Retryable
beans" - Declarative Retry - Spring Retry
Once getSubscriberAccount
has been invoked and execution has passed through the proxy and into the @Service
instance of the object, no reference to the proxy is known. As a result sphRemoteCall
is called as if there were no @Retryable
at all.
You could work with the framework by shuffling code around in such a way as to allow getSubscriberAccount
to call a proxy-ed sphRemoteCall
, which requires a new interface and class implementation.
For example:
public interface SphWebService {
Object sphRemoteCall(String uri, Object requestPayload, String soapAction);
}
@Component
public class SphWebServiceImpl implements SphWebService {
@Retryable
public Object sphRemoteCall(String uri, Object requestPayload, String soapAction) {
log.debug("Calling the sph for uri:{} and soapAction:{}", uri, soapAction);
return getWebServiceTemplate().marshalSendAndReceive(uri, requestPayload, new SoapActionCallback(soapAction));
}
}
@Service
public class SphIptvClient extends WebServiceGatewaySupport {
@Autowired
SphWebService sphWebService;
@Retryable(maxAttempts=3, backoff=@Backoff(delay=100))
public GetSubscriberAccountResponse getSubscriberAccount(String loginTocken, String billingServId) {
GetSubscriberAccountResponse response = (GetSubscriberAccountResponse) this.sphWebService.sphRemoteCall(sphIptvEndPoint, getSubAcc, "xxxxx");
return response;
}
}
@Configuration
@EnableRetry
public class SphClientConfig {
// the @Bean method was unnecessary and may cause confusion.
// @Service was already instantiating SphIptvClient behind the scenes.
}
@Retryable only works on the methods when called directly from other classes. If you will try to invoke one method with @Retryable annotation from some other method of the same class, it will eventually not work.
// any call from this method to test method will not invoke the retry logic.
public void yetAnotherMethod() {
this.test();
}
// it will work
@Retryable(value = {RuntimeException.class}, backoff = @Backoff(delay = 1500))
public void test() {
System.out.println("Count: " + count++);
throw new RuntimeException("testing");
}
@Recover
public void recover() {
System.out.println("Exception occured.");
}
So, the output if test method is called, will be:
Count: 0
Count: 1
Count: 2
Exception occured.
But, if the yetAnotherMethod is called, output will be:
Count: 0
And a Runtime exception will be thrown.