Как ответить на ошибку 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.
9 ответов:
изменить тип возврата до
ResponseEntity<>, то вы можете использовать ниже для 400return 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или что-то.
я бы немного изменил реализацию:
во-первых, я создаю
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:
Comments