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

Я использую Spring MVC для простого JSON API, @ResponseBody подход на основе @ResponseBody как @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 eg 400 "bad request"? } return json; } 

Вопрос в данном сценарии – самый простой и самый чистый способ ответить с ошибкой HTTP 400 ?

Я встретил такие подходы, как:

 return new ResponseEntity(HttpStatus.BAD_REQUEST); 

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

измените свой тип возврата на 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; } 

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

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

Редактировать, вы можете использовать @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 , которое будет распознано Spring ResponseStatusExceptionResolver от Spring. Если исключение выбрано, оно создаст ответ с соответствующим статусом ответа. (Я также взял на себя смелость изменить код состояния на 404 - Not Found который я считаю более подходящим для этого HttpStatus.BAD_REQUEST использования, но вы можете придерживаться HttpStatus.BAD_REQUEST если хотите.)


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

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

Наконец, я MappingJackson2HttpMessageConverter controller и передал Spring MappingJackson2HttpMessageConverter для автоматической обработки сериализации JSON (добавляется по умолчанию, если вы добавите Jackson в @EnableWebMvc к classам и добавьте в @EnableWebMvc либо @EnableWebMvc либо , см. справочные документы ):

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

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

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

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

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

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

Затем я создаю class рекомендаций controllerа

 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/

Я использую это в своем приложении загрузки весной

 @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(HttpStatus.BAD_REQUEST); } return new ResponseEntity(p, HttpStatus.OK); } 

С Spring Boot я не совсем уверен, почему это было необходимо (я получил @ResponseBody /error хотя @ResponseBody был определен в @ExceptionHandler ), но следующее само по себе не @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  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("No converter found for return value of type: " + valueType); // <-- throws } // .... @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); 

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

 @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("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, которые предоставляет Spring:

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

  • Игнорировать поля из объекта Java динамически при отправке в виде JSON из Spring MVC
  • UTF-8 в Spring MVC, проблема с формами FORM
  • запросить бобовые бобы весенним тестированием
  • Spring Web MVC - проверка отдельных параметров запроса
  • Бесконечная recursion с выпуском Jackson JSON и Hibernate JPA
  • Доступ к областям профилированных объектов в streamах
  • Как настроить имена параметров при связывании объектов командной строки spring mvc
  • Когда использовать antMatcher ()?
  • Обновление статического содержимого с помощью Spring MVC и Boot
  • Форма представляет в Spring MVC 3 - объяснение
  • $ {employee.id} из списка в JSP throws java.lang.NumberFormatException: для строки ввода: "id"
  • Давайте будем гением компьютера.