I have this org.springframework.web.client.RestTemplate code used in old Spring boot microservices configured to work with Eureka client/Server. I'm trying to make it work with org.springframework.cloud:spring-cloud-kubernetes-client-discovery into Kubernetes.
In this code I get incorrect code execution:
try {
ResponseEntity<AccountGroup> response =
connector.getAccountGroup(accountGroup.getValue());
if (response != null && HttpStatus.NOT_FOUND.equals(response.getStatusCode())) {
response = connector.exchange(............);
}
if (response == null || !response.getStatusCode().is2xxSuccessful()) {
// call this methid to create a group
LOGGER.error("Error while creating group ", accountGroup.getValue());
}
return repository.save(accountGroup);
} catch (DataIntegrityViolationException e) {
throw new BadRequestException();
}
// body of method getAccountGroup(....)
private RestTemplate clientRestTemplate;
@Retryable(maxAttemptsExpression = "3",
value = ResourceAccessException.class,
backoff = @Backoff(
delayExpression = "1000",
maxDelayExpression = "10000",
multiplierExpression = "1")
)
public <T> ResponseEntity<T> exchange(...........) {
return clientRestTemplate.exchange(url, method, requestEntity, responseType, uriVariables);
}
@Recover
public <T> ResponseEntity<T> recoverExchange(Exception exception, String service, String url, HttpMethod method,
HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws Exception {
..................
throw exception;
}
Into logs I see:
o.s.web.client.RestTemplate: Response 404 NOT_FOUND.
This is correct response from the target service. Now the code "// call this method to create a group " should be called but it's not. I get error:
org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method
Do you know how this issue can be solved?
Edit: Code example: https://github.com/rcbandit111/error_handler_poc/blob/main/parent/src/main/java/com/parent/web/rest/controller/RetryableRestTemplate.java#L31
There are several issues in your provided GitHub project. I'll start with the less obvious problem: Spring tries to find matching recover methods by comparing signatures. You're using parameterized return types with different generic types T being declared for each individual method:
1: @Retryable
public <T> ResponseEntity<T> exchange(String service, String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables)
2: @Recover
public <T> ResponseEntity<T> recoverExchange(Exception exception, String service, String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables)
If you move the type declaration <T> to class level, the T will be equal for both methods and Spring should find the matching recover method. The complete RetryableRestTemplate then looks like this:
package com.parent.web.rest.controller;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
public class RetryableRestTemplate<T> {
private RestTemplate clientRestTemplate;
private DiscoveryClient discoveryClient;
public RetryableRestTemplate(
RestTemplate clientRestTemplate,
DiscoveryClient discoveryClient) {
this.clientRestTemplate = clientRestTemplate;
this.discoveryClient = discoveryClient;
}
@Retryable(maxAttemptsExpression = "3",
retryFor = ResourceAccessException.class,
backoff = @Backoff(
delayExpression = "1000",
maxDelayExpression = "10000",
multiplierExpression = "1")
)
public ResponseEntity<T> exchange(
String service, String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) {
return clientRestTemplate.exchange(url, method, requestEntity, responseType, uriVariables);
}
@Recover
public ResponseEntity<T> recoverExchange(
Exception exception, String service, String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws Exception {
// some code here
throw exception;
}
}
Regarding the other issues I can only assume that you provided a minimal example but removed too much to make the example project work. I made the following changes before I could reproduce your question:
add @EnableRetry(proxyTargetClass = true) on the Application class
add implementation 'org.springframework:spring-aspects:6.1.5' to the build.gradle of the parent project
changed the url in RestClient#invoke to return retryableRestTemplate.exchange(service, "http://www.test.com", method, null, null);