Как ответить на ошибку HTTP 400 в методе Spring MVC @ResponseBody, возвращающем строку?



Я использую Spring MVC для простого JSON API, с @ResponseBody подход, как показано ниже. (У меня уже есть уровень обслуживания, производящий JSON напрямую.)



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


вопрос, в данном случае каков самый простой и чистый способ ответить на ошибку HTTP 400?



я наткнулся на такие подходы, как:



return new ResponseEntity(HttpStatus.BAD_REQUEST);


...но я не могу использовать его здесь, так как мой метод возвращает тип String, а не ResponseEntity.

944   9  

9 ответов:

изменить тип возврата до ResponseEntity<>, то вы можете использовать ниже для 400

return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

и для правильного запроса

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

обновление 1

после весны 4.1 есть вспомогательные методы в ResponseEntity может быть использован как

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

и

return ResponseEntity.ok(json);

что-то вроде этого должно работать, я не уверен, есть ли более простой способ:

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

не обязательно самый компактный способ сделать это, но довольно чистый IMO

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

Edit вы можете использовать @ResponseBody в методе обработчика исключений, если используете Spring 3.1+, в противном случае используйте ModelAndView или что-то.

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

я бы немного изменил реализацию:

во-первых, я создаю UnknownMatchException:

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

обратите внимание на использование @ResponseStatus, который будет признан весной ResponseStatusExceptionResolver. Если исключение будет вызвано, оно создаст ответ с соответствующим статусом ответа. (Я также взял на себя смелость изменить код состояния 404 - Not Found который я считаю более подходящим для данного варианта использования, но вы можете придерживаться HttpStatus.BAD_REQUEST если вы как.)


далее, я бы изменил MatchService иметь следующую подпись:

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

наконец, я хотел бы обновить контроллер и делегировать Spring's MappingJackson2HttpMessageConverter для автоматической обработки сериализации JSON (она добавляется по умолчанию, если вы добавляете Джексона в путь к классам и добавляете либо @EnableWebMvc или <mvc:annotation-driven /> к вашей конфигурации, см. справочные документы):

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

Примечание, это очень часто, чтобы отделить объекты домена из объектов представления или объектов DTO. Это может быть легко достигнуто путем добавления небольшой фабрики DTO, которая возвращает сериализуемый объект 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);
}

вот другой подход. Создайте пользовательский Exception аннотируется @ResponseStatus, как и следующий.

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

    public NotFoundException() {
    }
}

и бросить его, когда это необходимо.

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

ознакомьтесь с весенней документацией здесь: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-annotated-exceptions.

как упоминалось в некоторых ответах, есть возможность создать класс исключений для каждого состояния HTTP, которое вы хотите вернуть. Мне не нравится идея создания класса для каждого статуса для каждого проекта. Вот что я придумал вместо.

  • создать общее исключение, которое принимает статус HTTP
  • создать обработчик исключений Советов контроллера

давайте перейдем к коду

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

затем я создаю контроллер совет класса

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

использовать

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

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

Я использую это в моем приложении spring boot

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

С Spring Boot, я не совсем уверен, почему это было необходимо (я получил /error запасной вариант, хотя @ResponseBody был определен на @ExceptionHandler), но само по себе следующее не сработало:

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

он по-прежнему вызывал исключение, по-видимому, потому, что никакие производимые типы носителей не были определены как атрибут запроса:

// 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);

поэтому я добавил их.

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

и это помогло мне получить "поддерживаемый совместимый тип носителя", но тогда он все еще не получилось, потому что мой ErrorMessage был неисправен:

public class ErrorMessage {
    int code;

    String message;
}

JacksonMapper не обрабатывал его как "конвертируемый", поэтому мне пришлось добавить геттеры/сеттеры, и я также добавил @JsonProperty аннотации

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

затем я получил свое сообщение, как и предполагалось

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

Я думаю, что этот поток на самом деле имеет самое простое и чистое решение, которое не жертвует инструментами JSON martialing, которые предоставляет Spring:

https://stackoverflow.com/a/16986372/1278921

Comments

    Ничего не найдено.