如何响应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
或其他东西。
我会稍微改变实施:
首先,我创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门户工具:
- Android中的AsyncTask的通用类?
- 差异和何时使用getApplication(),getApplicationContext(),getBaseContext()和someClass.this