如何响应HTTP 400错误在Spring MVC @ResponseBody方法返回string?

我正在使用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 eg 400 "bad request"? } return json; } 

问题是,在给定的情况下, 什么是最简单,最干净的方式来应对HTTP 400错误

我遇到过如下的方法:

 return new ResponseEntity(HttpStatus.BAD_REQUEST); 

…但我不能在这里使用它,因为我的方法的返回types是string,而不是ResponseEntity。

改变你的返回types为ResponseEntity<> ,那么你可以使用下面的400

 return new ResponseEntity<>(HttpStatus.BAD_REQUEST); 

并为正确的要求

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

更新1

在4.1之后,ResponseEntity中可以使用helper方法

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

编辑你可以在exception处理程序方法中使用@ResponseBody如果使用Spring 3.1+,否则使用ModelAndView或其他东西。

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

我会稍微改变实施:

首先,我创build一个UnknownMatchException

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

请注意使用@ResponseStatus ,它将被Spring的ResponseStatusExceptionResolver识别。 如果抛出exception,它将创build一个具有相应响应状态的响应。 (我也冒昧地把状态代码改为404 - Not Found ,我觉得这个用例更适合,但是如果你愿意,可以坚持到HttpStatus.BAD_REQUEST 。)


接下来,我将更改MatchService以具有以下签名:

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

最后,我将更新控制器和委托到Spring的MappingJackson2HttpMessageConverter来自动处理JSON序列化(如果您将Jackson添加到类path中,并将@EnableWebMvc<mvc:annotation-driven />到您的configuration,则默认添加参考文件 ):

 @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对象分开是非常常见的。 这可以通过添加一个返回可序列化的JSON对象的小型DTO工厂轻松实现:

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

这是一个不同的方法。 创build一个用@ResponseStatus注解的自定义Exception ,如下所示。

 @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 。

正如在一些答案中提到的那样,可以为每个要返回的HTTP状态创build一个exception类。 我不喜欢为每个项目创build每个状态类的想法。 这是我想出来的。

  • 创build一个接受HTTP状态的通用exception
  • 创build一个Controller Adviceexception处理程序

让我们来看看代码

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

然后我创build一个控制器build议类

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

使用Spring Boot,我不完全确定为什么这是必要的(即使在@ResponseBody上定义了@ExceptionHandler ,我也得到了/error fallback),但是下面的代码本身不起作用:

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

它仍然抛出一个exception,显然是因为没有可生产的媒体types被定义为请求属性:

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

这让我有一个“支持兼容的媒体types”,但它仍然没有工作,因为我的ErrorMessage是错误的:

 public class ErrorMessage { int code; String message; } 

JacksonMapper并没有把它作为“可转换”来处理,所以我不得不添加getters / setters,而且我还添加了@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."} 

我认为这个线程实际上有最简单,最干净的解决scheme,不会牺牲Spring提供的JSON门户工具:

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