提问人:Jonik 提问时间:4/26/2013 最后编辑:Peter MortensenJonik 更新时间:7/6/2022 访问量:527170
如何在返回字符串的 Spring MVC @ResponseBody方法中响应 HTTP 400 错误
How to respond with an HTTP 400 error in a Spring MVC @ResponseBody method returning String
问:
我正在使用 Spring MVC 作为一个简单的 JSON API,其基于的方法如下所示。(我已经有一个直接生成 JSON 的服务层。@ResponseBody
@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。
答:
这不一定是最紧凑的方式,但在我看来相当干净:
if(json == null) {
throw new BadThingException();
}
...
@ExceptionHandler(BadThingException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public @ResponseBody MyError handleException(BadThingException e) {
return new MyError("That doesn’t work");
}
如果使用 Spring 3.1+,则可以在异常处理程序方法中使用 @ResponseBody,否则使用 a 或其他东西。ModelAndView
@ResponseBody不适用于@ExceptionHandler [SPR-6902] #11567
评论
ERROR org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Failed to invoke @ExceptionHandler method: public controller.TestController$MyError controller.TestController.handleException(controller.TestController$BadThingException) org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
javax.validation.ValidationException
这样的东西应该有效,但我不确定是否有更简单的方法:
@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;
}
评论
body
request
将返回类型更改为 ,然后您可以将以下内容用于 400:ResponseEntity<>
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
对于正确的请求:
return new ResponseEntity<>(json,HttpStatus.OK);
在 Spring 4.1 之后,ResponseEntity 中有一些辅助方法,可以用作:
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
和
return ResponseEntity.ok(json);
评论
ResponseEntity
我会稍微改变实现:
首先,我创建一个:UnknownMatchException
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UnknownMatchException extends RuntimeException {
public UnknownMatchException(String matchId) {
super("Unknown match: " + matchId);
}
}
请注意 @ResponseStatus 的使用,Spring 的 .如果抛出异常,它将创建具有相应响应状态的响应。(我还冒昧地更改了我认为更适合此用例的状态代码,但如果您愿意,您可以坚持使用。ResponseStatusExceptionResolver
404 - Not Found
HttpStatus.BAD_REQUEST
接下来,我将更改为具有以下签名:MatchService
interface MatchService {
public Match findMatch(String matchId);
}
最后,我会更新控制器并委托给 Spring 以自动处理 JSON 序列化(如果您将 Jackson 添加到类路径并添加其中任何一个或您的配置,则默认情况下会添加它。请参阅参考文档):MappingJackson2HttpMessageConverter
@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 对象分开是很常见的。这可以通过添加一个返回可序列化的 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);
}
评论
Match
@ResponseStatus
ResponseStatusException
我正在我的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);
}
评论
这是一种不同的方法。创建一个带有 注释的自定义,如下所示。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;
}
评论
@ResponseStatus
ResponseStatusException
正如某些答案中提到的,可以为要返回的每个 HTTP 状态创建一个异常类。我不喜欢必须为每个项目创建每个状态的类的想法。这是我想出的。
- 创建接受 HTTP 状态的泛型异常
- 创建 Controller Advice 异常处理程序
让我们来看看代码
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,我不完全确定为什么这是必要的(即使我得到了回退,即使定义在 ),但以下内容本身不起作用:/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 没有将其处理为“可转换”,所以我不得不添加 getters/setter,并且我还添加了注释@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."}
你也可以抛出新的 HttpMessageNotReadableException(“error description”)
来从 Spring 的默认错误处理中受益。
但是,与这些默认错误一样,不会设置响应正文。
我发现这些在拒绝只能合理地手工制作的请求时很有用,这可能表明恶意意图,因为它们掩盖了基于更深入的自定义验证及其标准拒绝请求的事实。
评论
HttpMessageNotReadableException("error description")
已弃用。
最简单的方法是抛出一个:ResponseStatusException
@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId, @RequestBody String body) {
String json = matchService.getMatchJson(matchId);
if (json == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
return json;
}
评论
另一种方法是使用 with 将所有处理程序集中到同一个类中。否则,必须将处理程序方法放在要为其管理异常的每个控制器中。@ExceptionHandler
@ControllerAdvice
处理程序类:
@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(MyBadRequestException.class)
public ResponseEntity<MyError> handleException(MyBadRequestException e) {
return ResponseEntity
.badRequest()
.body(new MyError(HttpStatus.BAD_REQUEST, e.getDescription()));
}
}
您的自定义例外:
public class MyBadRequestException extends RuntimeException {
private String description;
public MyBadRequestException(String description) {
this.description = description;
}
public String getDescription() {
return this.description;
}
}
现在,您可以从任何控制器引发异常,并且可以在建议类中定义其他处理程序。
使用带有状态代码的自定义响应。
喜欢这个:
class Response<T>(
val timestamp: String = DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS")
.withZone(ZoneOffset.UTC)
.format(Instant.now()),
val code: Int = ResultCode.SUCCESS.code,
val message: String? = ResultCode.SUCCESS.message,
val status: HttpStatus = HttpStatus.OK,
val error: String? = "",
val token: String? = null,
val data: T? = null
) : : ResponseEntity<Response.CustomResponseBody>(status) {
data class CustomResponseBody(
val timestamp: String = DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS")
.withZone(ZoneOffset.UTC)
.format(Instant.now()),
val code: Int = ResultCode.SUCCESS.code,
val message: String? = ResultCode.SUCCESS.message,
val error: String? = "",
val token: String? = null,
val data: Any? = null
)
override fun getBody(): CustomResponseBody? = CustomResponseBody(timestamp, code, message, error, token, data)
在控制器中处理异常而不必显式返回的最简单、最干净的方法是添加方法。ResponseEntity
@ExceptionHandler
使用 Spring Boot 2.0.3.RELEASE 的示例代码片段:
// Prefer static import of HttpStatus constants as it's cleaner IMHO
// Handle with no content returned
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(BAD_REQUEST)
void onIllegalArgumentException() {}
// Return 404 when JdbcTemplate does not return a single row
@ExceptionHandler(IncorrectResultSizeDataAccessException.class)
@ResponseStatus(NOT_FOUND)
void onIncorrectResultSizeDataAccessException() {}
// Catch all handler with the exception as content
@ExceptionHandler(Exception.class)
@ResponseStatus(I_AM_A_TEAPOT)
@ResponseBody Exception onException(Exception e) {
return e;
}
顺便说一句:
如果在所有上下文/用法中都是无效的,那么我的建议是抛出一个异常,例如,而不是返回并让它冒泡到控制器的 .
matchService.getMatchJson(matchId) == null
getMatchJson
IllegalArgumentException
null
@ExceptionHandler
如果用于测试其他条件,那么我将有一个特定的方法,例如, .一般来说,如果可能的话,我会避免,以避免意外。
null
matchService.hasMatchJson(matchId)
null
NullPointerException
评论