Estoy usando MVC de Spring para un simple API JSON, con @ResponseBody enfoque basado en como la siguiente. (Ya tengo una capa de servicio de producción de JSON directamente).

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

Cuestión es que, en la situación dada, ¿cuál es la más sencilla, limpia manera de responder con un error HTTP 400?

Me hizo venir a través de enfoques como:

return new ResponseEntity(HttpStatus.BAD_REQUEST);

…pero no los puedo usar aquí desde mi método del tipo de retorno es la Cadena, no ResponseEntity.

InformationsquelleAutor Jonik | 2013-04-26

10 Comentarios

  1. 581

    cambiar su tipo de retorno a ResponseEntity<>, entonces usted puede utilizar a continuación para 400

    return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

    y para la correcta solicitud de

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

    ACTUALIZACIÓN de 1

    después de la primavera 4.1 existen métodos auxiliares en ResponseEntity podría ser utilizado como

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

    y

    return ResponseEntity.ok(json);
    • Ah, así que usted puede utilizar ResponseEntity como esto demasiado. Esto funciona muy bien y es un sólo un simple cambio en el código original—gracias!
    • usted es bienvenido en cualquier momento usted puede agregar encabezado personalizado demasiado verificación de todos los constructores de ResponseEntity
    • opcionalmente, se puede proporcionar una respuesta del organismo, incluso para los códigos de respuesta además de HttpStatus.OK. funciona.
    • Lo que si va a pasar algo distinto de una cadena? Como en un POJO u otro objeto?
    • será ‘ResponseEntity<YourClass>’
    • Con este enfoque no necesita @ResponseBody anotación más
    • Incluso con esto, puedo obtener una respuesta 200 de estado de Server responded with a response on thread XNIO-3 task-2 2 < 200 2 < Content-Type: application/json aunque el cuerpo de la respuesta contiene un statusCodeValue que se establece en 400 – { "headers": {}, "body": null, "statusCode": "BAD_REQUEST", "statusCodeValue": 400 }
    • esta respuesta es de 5 años atrás y se puede ver que es aceptada por gran número suficiente para probar que es trabajar con ellos, puede ser que usted no está bien implementada o en el marco de la versión que se utiliza tiene un problema, por favor comparta su código en github y déjame comprobarlo.

  2. 98

    Algo como esto debería funcionar, no estoy seguro de si hay o no hay una manera más sencilla:

    @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;
    }
    • Gracias! Esto funciona y es bastante simple también. (En este caso podría ser más simplificado, mediante la eliminación de los no utilizados body y request params.)
  3. 53

    No necesariamente el más compacto manera de hacer esto, pero bastante limpio de la OMI

    if(json == null) {
        throw new BadThingException();
    }
    ...
    
    @ExceptionHandler(BadThingException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public @ResponseBody MyError handleException(BadThingException e) {
        return new MyError("That doesnt work");
    }

    Editar puede utilizar @ResponseBody en el controlador de excepciones método si usar Spring 3.1+, de lo contrario usar ModelAndView o algo.

    https://jira.springsource.org/browse/SPR-6902

    • Lo siento, esto no parece funcionar. Se produce HTTP 500 «error del servidor» con largo seguimiento de la pila de registros: 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 Es que hay algo que falta en la respuesta?
    • Además, yo no entiendo completamente el punto de la definición de otro tipo personalizado (MyError). Es que es necesario? Yo estoy usando la última Primavera (3.2.2).
    • A mí me funciona. Yo uso javax.validation.ValidationException lugar. (Primavera 3.1.4)
    • Esto es muy útil en situaciones donde usted tiene una capa intermedia entre el servicio y el cliente, donde la capa intermedia tiene su propio error capacidades de manejo. Gracias por este ejemplo @Zutty
    • Esto debe ser aceptado respuesta, a medida que avanza el tratamiento de excepciones en el código de la circulación normal y se oculta HttpServlet*
    • No funciona para mí la Primavera de Arranque y la verdad, no sé por qué
    • Esta respuesta es agradable porque se permite que el rendimiento normal de tipo y el error de caso de tipo de retorno será completamente diferente. (Tengo una función que normalmente devuelve un Dog objeto que recibe JSON serializado en la Primavera de Arranque, pero cuando la solicitud es malo, vuelvo completamente diferente Error-tipo de mensaje, no relacionados con la Dog.)

  4. 47

    Me iba a cambiar la implementación ligeramente:

    En primer lugar, crear un UnknownMatchException:

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

    Nota el uso de @ResponseStatus, que será reconocido por la Primavera del ResponseStatusExceptionResolver. Si se produce la excepción, va a crear una respuesta con el correspondiente estado de la respuesta. (También me tomé la libertad de cambiar el código de estado para 404 - Not Found que me parece más apropiado para este caso de uso, pero usted puede pegarse a HttpStatus.BAD_REQUEST si lo desea).


    Siguiente, me gustaría cambiar el MatchService a tener la siguiente firma:

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

    Por último, me gustaría actualizar el controlador y el delegado de la Primavera MappingJackson2HttpMessageConverter para controlar la serialización JSON automáticamente (por defecto se añade si se agrega Jackson a la ruta de clases y agregar @EnableWebMvc o <mvc:annotation-driven /> a su configuración, consulte la referencia docs):

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

    Nota, es muy común separar los objetos de dominio de los objetos de la vista o DTO objetos. Esto se puede lograr fácilmente mediante la adición de una pequeña DTO fábrica que devuelve el serializable objeto JSON:

    @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);
    }
    • Tengo 500 y me registros: ay 28, 2015 5:23:31 PM org.apache.cxf.interceptor.AbstractFaultChainInitiatorObserver onMessage GRAVE: Error durante el manejo de errores, se dé por vencido! org.apache.cxf.interceptor.La culpa
    • Solución perfecta, sólo quiero añadir que espero que el DTO es una composición de Match y algún otro objeto.
  5. 31

    Aquí es un enfoque diferente. Crear una personalizada Exception anotado con @ResponseStatus, como la siguiente.

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

    Y tiro de él cuando sea necesario.

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

    Retirar el Resorte de la documentación aquí: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-annotated-exceptions.

    • Este enfoque le permite terminar la ejecución dondequiera que esté en el stacktrace sin tener que devolver un «valor especial» que debe especificar el código de estado HTTP que desea devolver.
  6. 18

    Como se ha mencionado en algunas de las respuestas, existe la posibilidad de crear una excepción de la clase para cada estado HTTP que desea devolver. No me gusta la idea de tener que crear una clase por el estado para cada proyecto. Aquí está lo que encontré en su lugar.

    • Crear una excepción genérica que acepta un estado HTTP
    • Crear un Controlador de Asesoramiento controlador de excepción

    Vamos a llegar a el código

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

    Luego me cree un controlador de asesoramiento clase

    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());
        }
    }

    Usarlo

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

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

    • Método muy bueno.. en Lugar de una simple Cadena de caracteres prefiero devolver un jSON con el código de error y el mensaje de los campos..
    • Esta debe ser la respuesta correcta, una genérica y global controlador de excepción con la costumbre de código de estado y el mensaje 😀
  7. 10

    I m de usar esto en mi primavera de inicio de la aplicación

    @RequestMapping(value = "/matches/{matchId}", produces = "application/json")
    @ResponseBody
    public ResponseEntity<?> match(@PathVariable String matchId, @RequestBody String body,
                HttpServletRequest request, HttpServletResponse response) {
    
        Product p;
        try {
          p = service.getProduct(request.getProductId());
        } catch(Exception ex) {
           return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
        }
    
        return new ResponseEntity(p, HttpStatus.OK);
    }
  8. 0

    Con Spring Boot, no estoy del todo seguro de por qué esto era necesario (tengo el /error de reserva aunque @ResponseBody fue definido en un @ExceptionHandler), pero el siguiente en sí mismo no funciona:

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

    Todavía lanzó una excepción, al parecer, porque no producible tipos de medios se define como un atributo de la solicitud:

    //AbstractMessageConverterMethodProcessor
    @SuppressWarnings("unchecked")
    protected <T> 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<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
        List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
    if (value != null && producibleMediaTypes.isEmpty()) {
            throw new IllegalArgumentException("No converter found for return value of type: " + valueType);   //<-- throws
        }
    
    //....
    
    @SuppressWarnings("unchecked")
    protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, Type declaredType) {
        Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
        if (!CollectionUtils.isEmpty(mediaTypes)) {
            return new ArrayList<MediaType>(mediaTypes);

    Así que he añadido a ellos.

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

    Y esto me puso a través de un «apoyo de medios compatibles con el tipo», pero entonces aún no funciona, porque mi ErrorMessage era defectuosa:

    public class ErrorMessage {
        int code;
    
        String message;
    }

    JacksonMapper no manejar como «convertible», así que he tenido que añadir getters/setters, y también he añadido @JsonProperty anotación

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

    Entonces he recibido mi mensaje como la intención de

    {"code":400,"message":"An \"url\" parameter must be defined."}
  9. 0

    Usted también podría simplemente throw new HttpMessageNotReadableException("descripción del error") a beneficio de la Primavera del manejo de errores por defecto.

    Sin embargo, como es el caso con los predeterminada errores, no hay respuesta del cuerpo se establece.

    Puedo encontrar estos útil cuando el rechazo de solicitudes que, razonablemente, sólo han sido hechos a mano, posiblemente indicando una malévola intención, ya que ocultan el hecho de que la solicitud fue rechazada basándose en un profundo, validación personalizada y sus criterios.

    Hth,
    dtk

    • HttpMessageNotReadableException("error description") está en desuso.

Dejar respuesta

Please enter your comment!
Please enter your name here