Freitag, Mai 03, 2019

Spring RestController und Exception Handling

Wer das erste mal einen klassischen Rest-Service mit Spring schreibt, also einen @RestController implementiert, der JSON (application/json; charset=utf-8) als Reponse ausliefert, kommt irgendwann auf die Idee, ebenfalls Fehlerzustände als JSON Response mit einem passenden HttpStatus Code auszuliefern.

Per Default erhält der Client den HTTP-Statuscode 500, wenn innerhalb des @RestController eine Exception geworfen wird. Das Ganze eingebettet in der Standardfehlerseite des Webservers. Als Antwort auf einen Restservice denkbar ungeeignet.

Eventuell möchte man einen Request auf die fachliche Korrektheit seiner Parameter überprüfen. Falls die Prüfung negativ ausfällt, sollen spezielle Http Status Codes (siehe Wikipedia HTTP StatusCodes) ausgeliefert werden. Z.B. könnte sich der Statuscode 400 BAD REQUEST oder 422 UNPROCESSABLE ENTITY für das Beispiel anbieten.

Wie sieht eine Implementierung dazu aus? Auf GitHub habe ich ein Beispielprojekt angelegt. Der Webservice nimmt Benutzer Registrierungen entgegen und vermerkt, ob der Benutzer dem Speichern von Cookies zugestimmt hat. Die Klasse de.awtools.registration.CookieController ist z.B. ein einfacher @RestController. Mit Spring hat man drei Möglichkeiten das beschriebene Problem zu lösen. Ich habe mich hier für die Lösung mit einem @ControllerAdvice entschieden. Das hat den Vorteil, dass dieser Advice für alle meine @RestController gilt. Der ControllerAdvice sieht folgendermaßen aus:

package de.awtools.registration.config;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import de.awtools.registration.RequestValidationException;

@ControllerAdvice
public class GlobalExceptionAdvice extends ResponseEntityExceptionHandler {

    @ExceptionHandler(value = { RequestValidationException.class })
    protected ResponseEntity<Object> handleConflict(Exception ex,
            WebRequest request) {

        String bodyOfResponse = "Unknown";
        if (ex instanceof RequestValidationException) {
            RequestValidationException rvex = (RequestValidationException) ex;
            bodyOfResponse = new StringBuilder().append('{')
                    .append("\"message\": \"")
                    .append(rvex.getValidation().getValidationCode().toString())
                    .append("\"").append('}').toString();
        }

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);

        return handleExceptionInternal(ex, bodyOfResponse,
                headers, HttpStatus.BAD_REQUEST, request);
    }

}

Hier werden alle Exceptions vom Typ RequestValidationException gefangen und ausgewertet. D.h. der Http-Statuscode 400 wird ausgeliefert und die Exception, bzw. die Nachricht aus der Exception, wird in ein einfaches JSON Objekt verpackt.

Weitere Beispiele und Erklärungen:

Keine Kommentare:

Kommentar veröffentlichen

AssertJ und java.util.List

AssertJ hat eine praktische Möglichkeit, Listen in JUnit Tests abzuprüfen. Insbesondere, wenn in der Liste komplexe Objekte abgelegt sind, s...