451 Stimmen

Wie reagiert man in einer Spring MVC @ResponseBody-Methode, die einen String zurückgibt, mit einem HTTP 400-Fehler?

Ich verwende Spring MVC für eine einfache JSON-API, mit einem @ResponseBody Ansatz wie folgt. (Ich habe bereits eine Service-Schicht, die JSON direkt erstellt.)

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        // TODO: Wie kann man z.B. mit 400 "Bad Request" antworten?
    }
    return json;
}

In dem gegebenen Szenario, was ist der einfachste, sauberste Weg, um mit einem HTTP 400 Fehler zu antworten?

Ich bin auf Ansätze wie die folgenden gestoßen:

return new ResponseEntity(HttpStatus.BAD_REQUEST);

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

36voto

MevlütÖzdemir Punkte 2604

Der einfachste Weg ist, eine ResponseStatusException zu werfen:

@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;
}

25voto

Norris Punkte 635

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

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

Jetzt zum Code

package com.javaninja.cam.exception;

import org.springframework.http.HttpStatus;

/**
 * Die Ausnahme, die verwendet wird, um einen Status und eine Nachricht an das aufrufende System zurückzugeben.
 * @author norrisshelton
 */
@SuppressWarnings("ClassWithoutNoArgConstructor")
public class ResourceException extends RuntimeException {

    private HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

    /**
     * Ruft den HTTP-Statuscode ab, der an das aufrufende System zurückgegeben werden soll.
     * @return HTTP-Statuscode. Standardmäßig HttpStatus.INTERNAL_SERVER_ERROR (500).
     * @see HttpStatus
     */
    public HttpStatus getHttpStatus() {
        return httpStatus;
    }

    /**
     * Konstruiert eine neue Laufzeitausnahme mit dem angegebenen HttpStatus-Code und Detailnachricht.
     * Die Ursache ist nicht initialisiert und kann später durch einen Aufruf von {@link #initCause} initialisiert werden.
     * @param httpStatus der HTTP-Status. Die Detailnachricht wird für spätere Abruf durch die {@link #getHttpStatus()} Methode gespeichert.
     * @param message die Detailnachricht. Die Detailnachricht wird für spätere Abruf durch die {@link #getMessage()} Methode gespeichert.
     * @see HttpStatus
     */
    public ResourceException(HttpStatus httpStatus, String message) {
        super(message);
        this.httpStatus = httpStatus;
    }
}

Dann erstelle ich eine Controller Advice-Klasse

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-Klasse für alle SpringMVC-Controller.
 * @author norrisshelton
 * @see org.springframework.web.bind.annotation.ControllerAdvice
 */
@org.springframework.web.bind.annotation.ControllerAdvice
public class ControllerAdvice {

    /**
     * Behandelt ResourceExceptions für die SpringMVC-Controller.
     * @param e SpringMVC-Controller-Ausnahme.
     * @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 verwenden

throw new ResourceException(HttpStatus.BAD_REQUEST, "Meine Nachricht");

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

13voto

Aamir Faried Punkte 323

Ich benutze dies in meiner Spring Boot-Anwendung:

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public ResponseEntity match(@PathVariable String matchId, @RequestBody String body,
            HttpServletRequest request, HttpServletResponse response) {

    Produkt p;
    try {
      p = service.getProduct(request.getProductId());
    } catch(Exception ex) {
       return new ResponseEntity(HttpStatus.BAD_REQUEST);
    }

    return new ResponseEntity(p, HttpStatus.OK);
}

3voto

EpicPandaForce Punkte 75417

Mit Spring Boot bin ich mir nicht ganz sicher, warum das notwendig war (ich habe das /error-Fallback erhalten, obwohl @ResponseBody auf einem @ExceptionHandler definiert war), aber das Folgende hat für sich allein nicht funktioniert:

@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorMessage handleIllegalArguments(HttpServletRequest httpServletRequest, IllegalArgumentException e) {
    log.error("Ungültige Argumente erhalten.", e);
    ErrorMessage errorMessage = new ErrorMessage();
    errorMessage.code = 400;
    errorMessage.message = e.getMessage();
    return errorMessage;
}

Es hat immer noch eine Ausnahme ausgelöst, anscheinend weil keine produzierbaren Medientypen als Anforderungsattribut definiert wurden:

// AbstractMessageConverterMethodProcessor
@SuppressWarnings("unchecked")
protected  void writeWithMessageConverters(T value, MethodParameter returnType,
        ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

    Class valueType = getReturnValueType(value, returnType);
    Type declaredType = getGenericType(returnType);
    HttpServletRequest request = inputMessage.getServletRequest();
    List requestedMediaTypes = getAcceptableMediaTypes(request);
    List producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
if (value != null && producibleMediaTypes.isEmpty()) {
        throw new IllegalArgumentException("Kein Konverter gefunden für den Rückgabewert vom Typ: " + valueType);   // <-- wirft
    }

// ....

@SuppressWarnings("unchecked")
protected List getProducibleMediaTypes(HttpServletRequest request, Class valueClass, Type declaredType) {
    Set mediaTypes = (Set) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    if (!CollectionUtils.isEmpty(mediaTypes)) {
        return new ArrayList(mediaTypes);

Also habe ich sie hinzugefügt.

@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorMessage handleIllegalArguments(HttpServletRequest httpServletRequest, IllegalArgumentException e) {
    Set mediaTypes = new HashSet<>();
    mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
    httpServletRequest.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
    log.error("Ungültige Argumente erhalten.", e);
    ErrorMessage errorMessage = new ErrorMessage();
    errorMessage.code = 400;
    errorMessage.message = e.getMessage();
    return errorMessage;
}

Und das hat mich dazu gebracht, einen "unterstützten kompatiblen Medientyp" zu haben, aber dann hat es immer noch nicht funktioniert, weil mein ErrorMessage fehlerhaft war:

public class ErrorMessage {
    int code;

    String message;
}

JacksonMapper hat es nicht als "konvertierbar" behandelt, also musste ich Getter/Setter hinzufügen, und ich habe auch die @JsonProperty-Annotation hinzugefügt

public class ErrorMessage {
    @JsonProperty("code")
    private int code;

    @JsonProperty("message")
    private String message;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

Dann habe ich meine Nachricht wie beabsichtigt erhalten

{"code":400,"message":"Ein \"url\"-Parameter muss definiert sein."}

2voto

Gonzalo Punkte 1771

Ein weiterer Ansatz besteht darin, @ExceptionHandler mit @ControllerAdvice zu verwenden, um alle Handler in derselben Klasse zu zentralisieren. Andernfalls müssen Sie die Handler-Methoden in jedem Controller platzieren, für den Sie eine Ausnahme verwalten möchten.

Ihre Handler-Klasse:

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {

  @ExceptionHandler(MyBadRequestException.class)
  public ResponseEntity handleException(MyBadRequestException e) {
    return ResponseEntity
        .badRequest()
        .body(new MyError(HttpStatus.BAD_REQUEST, e.getDescription()));
  }
}

Ihre benutzerdefinierte Ausnahme:

public class MyBadRequestException extends RuntimeException {

  private String description;

  public MyBadRequestException(String description) {
    this.description = description;
  }

  public String getDescription() {
    return this.description;
  }
}

Jetzt können Sie Ausnahmen aus jedem Ihrer Controller werfen und andere Handler in Ihrer Beratungsklasse definieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X