So reagieren Sie mit einem HTTP 400-Fehler in einer @ResponseBody-Methode von Spring MVC, die String zurückgibt

Lesezeit: 7 Minuten

Benutzer-Avatar
Jonik

Ich verwende Spring MVC für eine einfache JSON-API mit @ResponseBody basierter Ansatz wie der folgende. (Ich habe bereits eine Dienstschicht, die JSON direkt produziert.)

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        // TODO: how to respond with e.g. 400 "bad request"?
    }
    return json;
}

Was ist im gegebenen Szenario der einfachste und sauberste Weg, um mit einem HTTP 400-Fehler zu reagieren?

Ich bin auf Ansätze gestoßen wie:

return new ResponseEntity(HttpStatus.BAD_REQUEST);

… aber ich kann es hier nicht verwenden, da der Rückgabetyp meiner Methode String ist, nicht ResponseEntity.

Benutzer-Avatar
Bassem Reda Zohdy

Ändern Sie Ihren Rückgabetyp in ResponseEntity<>und dann können Sie das Folgende für 400 verwenden:

return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

Und für eine korrekte Anfrage:

return new ResponseEntity<>(json,HttpStatus.OK);

Nach Spring 4.1 gibt es Hilfsmethoden in ResponseEntity, die verwendet werden könnten als:

return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);

und

return ResponseEntity.ok(json);

  • Ah, also kannst du verwenden ResponseEntity auch so. Das funktioniert gut und ist nur eine einfache Änderung des ursprünglichen Codes – danke!

    – Jonik

    27. April 2013 um 16:22 Uhr

  • Sie können jederzeit einen benutzerdefinierten Header hinzufügen, um alle Konstruktoren von ResponseEntity zu überprüfen

    – Bassem Reda Zohdy

    27. April 2013 um 22:27 Uhr

  • Was ist, wenn Sie etwas anderes als eine Zeichenfolge zurückgeben? Wie in einem POJO oder einem anderen Objekt?

    – Mrshickadance

    18. November 2014 um 16:28 Uhr

  • es wird „ResponseEntity“ sein.

    – Bassem Reda Zohdy

    19. November 2014 um 10:29 Uhr

  • Bei diesem Ansatz benötigen Sie keine @ResponseBody-Anmerkung mehr

    – Ilya Serbis

    3. August 2015 um 12:26 Uhr

Benutzer-Avatar
Stapler

So etwas sollte funktionieren, aber ich bin mir nicht sicher, ob es einen einfacheren Weg gibt oder nicht:

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId, @RequestBody String body,
            HttpServletRequest request, HttpServletResponse response) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        response.setStatus( HttpServletResponse.SC_BAD_REQUEST  );
    }
    return json;
}

  • Vielen Dank! Das funktioniert und ist auch ziemlich einfach. (In diesem Fall könnte es weiter vereinfacht werden, indem die unbenutzten entfernt werden body und request Parameter.)

    – Jonik

    27. April 2013 um 9:18 Uhr

Benutzer-Avatar
Zutty

Es ist nicht unbedingt die kompakteste Art, dies zu tun, aber meiner Meinung nach ziemlich sauber:

if(json == null) {
    throw new BadThingException();
}
...

@ExceptionHandler(BadThingException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public @ResponseBody MyError handleException(BadThingException e) {
    return new MyError("That doesn’t work");
}

Sie können @ResponseBody in der Exception-Handler-Methode verwenden, wenn Sie Spring 3.1+ verwenden, andernfalls verwenden Sie a ModelAndView oder so.

@ResponseBody funktioniert nicht mit @ExceptionHandler [SPR-6902] #11567

  • Tut mir leid, das scheint nicht zu funktionieren. Es erzeugt HTTP 500 “Serverfehler” mit langem Stack-Trace in Protokollen: ERROR org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Failed to invoke @ExceptionHandler method: public controller.TestController$MyError controller.TestController.handleException(controller.TestController$BadThingException) org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation Fehlt etwas in der Antwort?

    – Jonik

    27. April 2013 um 8:52 Uhr


  • Außerdem habe ich den Sinn der Definition eines weiteren benutzerdefinierten Typs (MyError) nicht ganz verstanden. Ist das notwendig? Ich verwende den neuesten Spring (3.2.2).

    – Jonik

    27. April 2013 um 8:54 Uhr

  • Für mich geht das. ich benutze javax.validation.ValidationException stattdessen. (Frühjahr 3.1.4)

    – Jerry Chen

    1. Oktober 2013 um 1:48 Uhr


  • Dies ist sehr nützlich in Situationen, in denen Sie eine Zwischenschicht zwischen Ihrem Dienst und dem Client haben, in der die Zwischenschicht über eigene Fehlerbehandlungsfunktionen verfügt. Danke für dieses Beispiel @Zutty

    – StormeHawke

    9. Dezember 2014 um 15:57 Uhr

  • Dies sollte die akzeptierte Antwort sein, da sie den Ausnahmebehandlungscode aus dem normalen Fluss verschiebt und HttpServlet* verbirgt.

    – lilalinux

    14. September 2016 um 12:17 Uhr


Benutzer-Avatar
matsev

Ich würde die Implementierung etwas ändern:

Zuerst erstelle ich eine UnknownMatchException:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UnknownMatchException extends RuntimeException {
    public UnknownMatchException(String matchId) {
        super("Unknown match: " + matchId);
    }
}

Beachten Sie die Verwendung von @Antwortstatusdie von Spring’s erkannt wird ResponseStatusExceptionResolver. Wenn die Ausnahme ausgelöst wird, wird eine Antwort mit dem entsprechenden Antwortstatus erstellt. (Ich habe mir auch die Freiheit genommen, den Statuscode zu ändern 404 - Not Found was ich für diesen Anwendungsfall geeigneter finde, aber Sie können sich daran halten HttpStatus.BAD_REQUEST wenn du möchtest.)


Als nächstes würde ich die ändern MatchService folgende Signatur haben:

interface MatchService {
    public Match findMatch(String matchId);
}

Schließlich würde ich den Controller aktualisieren und an Spring delegieren MappingJackson2HttpMessageConverter um die JSON-Serialisierung automatisch zu handhaben (sie wird standardmäßig hinzugefügt, wenn Sie Jackson zum Klassenpfad hinzufügen und entweder @EnableWebMvc oder <mvc:annotation-driven /> zu deiner Konfig. Siehe die Referenzdokumentation):

@RequestMapping(value = "/matches/{matchId}", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Match match(@PathVariable String matchId) {
    // Throws an UnknownMatchException if the matchId is not known
    return matchService.findMatch(matchId);
}

Beachten Sie, dass es sehr üblich ist, die Domänenobjekte von den Ansichtsobjekten zu trennen oder DTO Objekte. Dies kann leicht erreicht werden, indem eine kleine DTO-Factory hinzugefügt wird, die das serialisierbare JSON-Objekt zurückgibt:

@RequestMapping(value = "/matches/{matchId}", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public MatchDTO match(@PathVariable String matchId) {
    Match match = matchService.findMatch(matchId);
    return MatchDtoFactory.createDTO(match);
}

Hier ist ein anderer Ansatz. Erstellen Sie eine benutzerdefinierte Exception kommentiert mit @ResponseStatuswie die folgende.

@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Not Found")
public class NotFoundException extends Exception {

    public NotFoundException() {
    }
}

Und werfen Sie es, wenn es nötig ist.

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        throw new NotFoundException();
    }
    return json;
}

Schauen Sie sich die Spring-Dokumentation hier an: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-annotated-exceptions.

  • Mit diesem Ansatz können Sie die Ausführung beenden, wo immer Sie sich im Stacktrace befinden, ohne einen “speziellen Wert” zurückgeben zu müssen, der den HTTP-Statuscode angeben sollte, den Sie zurückgeben möchten.

    – Muhammad Gelbana

    28. November 2017 um 5:34 Uhr

  • Der Link ist (effektiv) unterbrochen. Da steht nichts über Ausnahmen.

    – Peter Mortensen

    gestern

Benutzer-Avatar
Peter Mortensen

Der einfachste Weg ist, a zu werfen ResponseStatusException:

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId, @RequestBody String body) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        throw new ResponseStatusException(HttpStatus.NOT_FOUND);
    }
    return json;
}

  • Mit diesem Ansatz können Sie die Ausführung beenden, wo immer Sie sich im Stacktrace befinden, ohne einen “speziellen Wert” zurückgeben zu müssen, der den HTTP-Statuscode angeben sollte, den Sie zurückgeben möchten.

    – Muhammad Gelbana

    28. November 2017 um 5:34 Uhr

  • Der Link ist (effektiv) unterbrochen. Da steht nichts über Ausnahmen.

    – Peter Mortensen

    gestern

Benutzer-Avatar
Norris

Wie in einigen Antworten erwähnt, besteht die Möglichkeit, eine Ausnahmeklasse für jeden HTTP-Status zu erstellen, den Sie zurückgeben möchten. Ich mag die Idee nicht, für jedes Projekt eine Klasse pro Status erstellen zu müssen. Hier ist, was ich mir stattdessen ausgedacht habe.

  • Erstellen Sie eine generische Ausnahme, die einen HTTP-Status akzeptiert
  • Erstellen Sie einen Controller Advice-Ausnahmehandler

Kommen wir zum Code

package com.javaninja.cam.exception;

import org.springframework.http.HttpStatus;


/**
 * The exception used to return a status and a message to the calling system.
 * @author norrisshelton
 */
@SuppressWarnings("ClassWithoutNoArgConstructor")
public class ResourceException extends RuntimeException {

    private HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

    /**
     * Gets the HTTP status code to be returned to the calling system.
     * @return http status code.  Defaults to HttpStatus.INTERNAL_SERVER_ERROR (500).
     * @see HttpStatus
     */
    public HttpStatus getHttpStatus() {
        return httpStatus;
    }

    /**
     * Constructs a new runtime exception with the specified HttpStatus code and detail message.
     * The cause is not initialized, and may subsequently be initialized by a call to {@link #initCause}.
     * @param httpStatus the http status.  The detail message is saved for later retrieval by the {@link
     *                   #getHttpStatus()} method.
     * @param message    the detail message. The detail message is saved for later retrieval by the {@link
     *                   #getMessage()} method.
     * @see HttpStatus
     */
    public ResourceException(HttpStatus httpStatus, String message) {
        super(message);
        this.httpStatus = httpStatus;
    }
}

Dann erstelle ich einen Controller-Beratungskurs

package com.javaninja.cam.spring;


import com.javaninja.cam.exception.ResourceException;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;


/**
 * Exception handler advice class for all SpringMVC controllers.
 * @author norrisshelton
 * @see org.springframework.web.bind.annotation.ControllerAdvice
 */
@org.springframework.web.bind.annotation.ControllerAdvice
public class ControllerAdvice {

    /**
     * Handles ResourceExceptions for the SpringMVC controllers.
     * @param e SpringMVC controller exception.
     * @return http response entity
     * @see ExceptionHandler
     */
    @ExceptionHandler(ResourceException.class)
    public ResponseEntity handleException(ResourceException e) {
        return ResponseEntity.status(e.getHttpStatus()).body(e.getMessage());
    }
}

Um es zu benutzen

throw new ResourceException(HttpStatus.BAD_REQUEST, "My message");

http://javaninja.net/2016/06/throwing-exceptions-messages-spring-mvc-controller/

  • Sehr gute Methode.. Anstelle eines einfachen Strings gebe ich lieber ein jSON mit errorCode und Message-Feldern zurück..

    – Ismail Yavuz

    13. Dezember 2017 um 14:29 Uhr

  • Dies sollte die richtige Antwort sein, ein generischer und globaler Ausnahmehandler mit benutzerdefiniertem Statuscode und Nachricht: D

    – Pedro Silva

    28. September 2018 um 16:36 Uhr

  • Browser verlinken den Link nicht: “Warnung: Mögliches Sicherheitsrisiko voraus … Das Zertifikat für javaninja.net ist am 18.11.2021 abgelaufen.”

    – Peter Mortensen

    gestern

1019570cookie-checkSo reagieren Sie mit einem HTTP 400-Fehler in einer @ResponseBody-Methode von Spring MVC, die String zurückgibt

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy