Spring Resttemplate la gestione delle eccezioni


124

Di seguito è riportato lo snippet di codice; fondamentalmente, sto cercando di propagare l'eccezione quando il codice di errore è diverso da 200.

ResponseEntity<Object> response = restTemplate.exchange(url.toString().replace("{version}", version),
                    HttpMethod.POST, entity, Object.class);
            if(response.getStatusCode().value()!= 200){
                logger.debug("Encountered Error while Calling API");
                throw new ApplicationException();
            }

Tuttavia, nel caso di una risposta 500 dal server ottengo l'eccezione

org.springframework.web.client.HttpServerErrorException: 500 Internal Server Error
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:94) ~[spring-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]

Ho davvero bisogno di includere il metodo di scambio del modello di rest in try? Quale sarebbe allora lo scopo dei codici?


Si prega di condividere il codice di ApplicationException ()
Mudassar

Risposte:


128

Vuoi creare una classe che implementa ResponseErrorHandlere quindi usarne un'istanza per impostare la gestione degli errori del tuo modello di riposo:

public class MyErrorHandler implements ResponseErrorHandler {
  @Override
  public void handleError(ClientHttpResponse response) throws IOException {
    // your error handling here
  }

  @Override
  public boolean hasError(ClientHttpResponse response) throws IOException {
     ...
  }
}

[...]

public static void main(String args[]) {
  RestTemplate restTemplate = new RestTemplate();
  restTemplate.setErrorHandler(new MyErrorHandler());
}

Inoltre, Spring ha la classe DefaultResponseErrorHandler, che puoi estendere invece di implementare l'interfaccia, nel caso in cui desideri solo sovrascrivere il handleErrormetodo.

public class MyErrorHandler extends DefaultResponseErrorHandler {
  @Override
  public void handleError(ClientHttpResponse response) throws IOException {
    // your error handling here
  }
}

Dai un'occhiata al suo codice sorgente per avere un'idea di come Spring gestisce gli errori HTTP.


1
Ho 1 istanza di RestTemplate che riutilizzo per chiamate diverse. Ho bisogno di gestire gli errori da diverse chiamate in modo diverso - apparentemente non c'è modo di farlo con il gestore globale - devo fornire un gestore per richiesta.
mvmn

4
Con questo gestore di errori ottengo sempre un ResourceAccessExceptionperché RestTemplate.doExecutecattura se IOExceptionlancia un file ResourceAccessException. Che cosa sto facendo di sbagliato?
Federico Bellucci

Sono stato in grado di risolvere questo problema estendendo DefaultResponseErrorHandler.
Crenguta S

48

Dovresti prendere HttpStatusCodeExceptionun'eccezione:

try {
    restTemplate.exchange(...);
} catch (HttpStatusCodeException exception) {
    int statusCode = exception.getStatusCode().value();
    ...
}

37
IMO la risposta dovrebbe sempre venire con un codice di stato appropriato, altrimenti qual è lo scopo dei codici.
vaibhav

5
Non sono sicuro di capire l'obiezione di @vaibhav: catturare HttpStatusCodeException non è per un codice sbagliato, ma perché in molti casi viene sempre generata un'eccezione e quindi il tuo if (codice == valore) non può mai essere eseguito.
Stefano Scarpanti

1
Le eccezioni sono molto costose in Java. Va bene per le cause occasionali e impreviste (da cui il nome), ma oltre a questo, dovresti invece cercare altre soluzioni.
Agoston Horvath

11
"Molto costoso"? Più costoso, ad esempio, di una chiamata HTTP?
IcedDante

4
@RaffaelBecharaRameh - HttpStatusCodeException .getResponseBodyAsString () o HttpStatusCodeException.getResponseBodyAsByteArray ().
Dave

45

Spring tratta abilmente i codici di errore http come eccezioni e presume che il codice di gestione delle eccezioni abbia il contesto per gestire l'errore. Per fare in modo che lo scambio funzioni come ti aspetteresti, fai questo:

    try {
        return restTemplate.exchange(url, httpMethod, httpEntity, String.class);
    } catch(HttpStatusCodeException e) {
        return ResponseEntity.status(e.getRawStatusCode()).headers(e.getResponseHeaders())
                .body(e.getResponseBodyAsString());
    }

Ciò restituirà tutti i risultati attesi dalla risposta.


2
è necessario utilizzare un HttpClient diverso da quello predefinito SDK, per ottenere il corpo della risposta per gli errori
razor

26

Un'altra soluzione è quella descritta qui alla fine di questo post da "Enlian": http://springinpractice.com/2013/10/07/handling-json-error-object-responses-with-springs-resttemplate

try{
     restTemplate.exchange(...)
} catch(HttpStatusCodeException e){
     String errorpayload = e.getResponseBodyAsString();
     //do whatever you want
} catch(RestClientException e){
     //no response payload, tell the user sth else 
}

4
è necessario utilizzare un HttpClient diverso da quello predefinito SDK, per ottenere il corpo della risposta per gli errori (ad esempio apache commons HttpClient)
razor

17

Spring ti astrae dall'elenco molto molto ampio di codici di stato http. Questa è l'idea delle eccezioni. Dai un'occhiata alla gerarchia di org.springframework.web.client.RestClientException:

Hai un sacco di classi per mappare le situazioni più comuni quando si tratta di risposte http. L'elenco dei codici http è molto grande, non vorrai scrivere codice per gestire ogni situazione. Ma ad esempio, dai un'occhiata alla sotto-gerarchia HttpClientErrorException. Hai un'unica eccezione per mappare qualsiasi tipo di errore 4xx. Se hai bisogno di andare in profondità, puoi farlo. Ma con la semplice cattura di HttpClientErrorException, puoi gestire qualsiasi situazione in cui sono stati forniti dati non validi al servizio.

DefaultResponseErrorHandler è davvero semplice e solido. Se il codice di stato della risposta non è della famiglia di 2xx, restituisce solo true per il metodo hasError.


Amico, grazie per la spiegazione. Come hai costruito questo albero con la gerarchia delle eccezioni?
stand alone

1
Ehi amico, ho usato Eclipse. Basta premere CTRL + MAIUSC + T per aprire il tipo di ricerca e digitare RestClientException. Fare doppio clic su RestClientException dai risultati, Eclipse aprirà quella classe per te. Quindi, posiziona il cursore del mouse sul nome della classe (dove dice "public class RestClientException ..." e premi control + T. Vedrai quella gerarchia.
Perimosh

Hai provato?
Perimosh

1
Btw in Intellij è: fai clic sulla classe nella struttura del progetto e Ctrl + Alt + U, o fai clic con il tasto destro del mouse -> Crea diagramma
stand alone

3

Se usi il meccanismo di pooling (http client factory) o di bilanciamento del carico (eureka) con il tuo RestTemplate, non avrai il lusso di creare un new RestTemplateper classe. Se chiami più di un servizio non puoi utilizzaresetErrorHandler perché verrebbe utilizzato globalmente per tutte le tue richieste.

In questo caso, catturando il file HttpStatusCodeException sembra essere l'opzione migliore.

L'unica altra opzione che hai è definire più RestTemplateistanze usando il@Qualifier annotazione.

Inoltre, ma questo è il mio gusto, mi piace che la mia gestione degli errori sia strettamente legata alle mie chiamate.


3

L'ho gestito come di seguito:

try {
  response = restTemplate.postForEntity(requestUrl, new HttpEntity<>(requestBody, headers), String.class);
} catch (HttpStatusCodeException ex) {
  response = new ResponseEntity<String>(ex.getResponseBodyAsString(), ex.getResponseHeaders(), ex.getStatusCode());
}

1

Il codice di scambio è di seguito :

public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
            HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException

Eccezione RestClientExceptionha HttpClientErrorExceptione HttpStatusCodeExceptionfa eccezione.

Quindi, in RestTempleteci può occure HttpClientErrorExceptione HttpStatusCodeExceptionfa eccezione. Nell'oggetto eccezione puoi ottenere un messaggio di errore esatto usando questo modo:exception.getResponseBodyAsString()

Ecco il codice di esempio :

public Object callToRestService(HttpMethod httpMethod, String url, Object requestObject, Class<?> responseObject) {

        printLog( "Url : " + url);
        printLog( "callToRestService Request : " + new GsonBuilder().setPrettyPrinting().create().toJson(requestObject));

        try {

            RestTemplate restTemplate = new RestTemplate();
            restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
            restTemplate.getMessageConverters().add(new StringHttpMessageConverter());


            HttpHeaders requestHeaders = new HttpHeaders();
            requestHeaders.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);

            long start = System.currentTimeMillis();

            ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);

            printLog( "callToRestService Status : " + responseEntity.getStatusCodeValue());


            printLog( "callToRestService Body : " + new GsonBuilder().setPrettyPrinting().create().toJson(responseEntity.getBody()));

            long elapsedTime = System.currentTimeMillis() - start;
            printLog( "callToRestService Execution time: " + elapsedTime + " Milliseconds)");

            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                return responseEntity.getBody();
            }

        } catch (HttpClientErrorException exception) {
            printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
            //Handle exception here
        }catch (HttpStatusCodeException exception) {
            printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
            //Handle exception here
        }
        return null;
    }

Ecco la descrizione del codice :

In questo metodo devi passare la richiesta e la classe di risposta. Questo metodo analizzerà automaticamente la risposta come oggetto richiesto.

Prima di tutto devi aggiungere un convertitore di messaggi.

restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
            restTemplate.getMessageConverters().add(new StringHttpMessageConverter());

Quindi devi aggiungere requestHeader. Ecco il codice:

HttpHeaders requestHeaders = new HttpHeaders();
            requestHeaders.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);

Infine, devi chiamare il metodo di scambio:

ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);

Per la stampa prety ho usato la libreria Gson. ecco il gradle:compile 'com.google.code.gson:gson:2.4'

Puoi semplicemente chiamare il codice seguente per ottenere una risposta:

ResponseObject response=new RestExample().callToRestService(HttpMethod.POST,"URL_HERE",new RequestObject(),ResponseObject.class);

Ecco il codice funzionante completo :

import com.google.gson.GsonBuilder;
import org.springframework.http.*;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;


public class RestExample {

    public RestExample() {

    }

    public Object callToRestService(HttpMethod httpMethod, String url, Object requestObject, Class<?> responseObject) {

        printLog( "Url : " + url);
        printLog( "callToRestService Request : " + new GsonBuilder().setPrettyPrinting().create().toJson(requestObject));

        try {

            RestTemplate restTemplate = new RestTemplate();
            restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
            restTemplate.getMessageConverters().add(new StringHttpMessageConverter());


            HttpHeaders requestHeaders = new HttpHeaders();
            requestHeaders.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);

            long start = System.currentTimeMillis();

            ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);

            printLog( "callToRestService Status : " + responseEntity.getStatusCodeValue());


            printLog( "callToRestService Body : " + new GsonBuilder().setPrettyPrinting().create().toJson(responseEntity.getBody()));

            long elapsedTime = System.currentTimeMillis() - start;
            printLog( "callToRestService Execution time: " + elapsedTime + " Milliseconds)");

            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                return responseEntity.getBody();
            }

        } catch (HttpClientErrorException exception) {
            printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
            //Handle exception here
        }catch (HttpStatusCodeException exception) {
            printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
            //Handle exception here
        }
        return null;
    }

    private void printLog(String message){
        System.out.println(message);
    }
}

Grazie :)


2
"org.springframework.web.client.HttpClientErrorException" è una sottoclasse di "org.springframework.web.client.HttpStatusCodeException". Non è necessario rilevare HttpClientErrorException se si sta già rilevando HttpStatusCodeException e non si sta facendo nulla di diverso nella gestione delle due eccezioni precedenti.
The-Proton-Resurgence

0

Una soluzione molto semplice può essere:

try {
     requestEntity = RequestEntity
     .get(new URI("user String"));
    
    return restTemplate.exchange(requestEntity, String.class);
} catch (RestClientResponseException e) {
        return ResponseEntity.status(e.getRawStatusCode()).body(e.getResponseBodyAsString());
}

-1

Ecco il mio metodo POST con HTTPS che restituisce un corpo di risposta per qualsiasi tipo di risposta negativa.

public String postHTTPSRequest(String url,String requestJson)
{
    //SSL Context
    CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);
    //Initiate REST Template
    RestTemplate restTemplate = new RestTemplate(requestFactory);
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    //Send the Request and get the response.
    HttpEntity<String> entity = new HttpEntity<String>(requestJson,headers);
    ResponseEntity<String> response;
    String stringResponse = "";
    try {
        response = restTemplate.postForEntity(url, entity, String.class);
        stringResponse = response.getBody();
    }
    catch (HttpClientErrorException e)
    {
        stringResponse = e.getResponseBodyAsString();
    }
    return stringResponse;
}

3
è necessario utilizzare un HttpClient diverso da quello predefinito SDK, per ottenere il corpo della risposta per gli errori (ad esempio apache commons HttpClient)
razor
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.