Spring MVC @ResponseBody方法返回字符串时,如何响应HTTP 400错误?


389

我将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。

Answers:


624

将返回类型更改为ResponseEntity<>,则可以在下面使用400

return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

和正确的要求

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

更新1

在Spring 4.1之后,ResponseEntity中有一些辅助方法可以用作

return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);

return ResponseEntity.ok(json);

嗯,所以您也可以这样使用ResponseEntity。这很好用,并且只是对原始代码的简单更改,谢谢!
Jonik

欢迎您随时可以添加自定义标题过于检查ResponseEntity的所有构造
的Bassem Reda的Zohdy

7
如果您传递的不是字符串,该怎么办?如在POJO或其他对象中?
mrshickadance

11
这将是'ResponseEntity <YourClass>'
Bassem Reda Zohdy 2014年

5
使用这种方法,您不再需要@ResponseBody注释
Lu55

108

这样的事情应该可以工作,我不确定是否有更简单的方法:

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

5
谢谢!这有效并且非常简单。(在这种情况下,它可进一步通过去除未使用的简化bodyrequestPARAMS。)
Jonik

54

不一定是最紧凑的方法,但很干净的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");
}

编辑(如果使用Spring 3.1+,则可以在异常处理程序方法中使用@ResponseBody,否则使用ModelAndView或)。

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


1
抱歉,这似乎无效。它会产生HTTP 500“服务器错误”,并在日志中带有长堆栈跟踪: 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答案中是否缺少某些内容?
Jonik

另外,我还没有完全理解定义另一个自定义类型(M​​yError)的意义。那有必要吗?我正在使用最新的Spring(3.2.2)。
Jonik

1
这个对我有用。我javax.validation.ValidationException改用。(春季3.1.4)
Jerry Chen

这在服务和客户端之间具有中间层且中间层具有自己的错误处理功能的情况下非常有用。感谢您的示例@Zutty
StormeHawke 2014年

这应该是一个可以接受的答案,因为它将异常处理代码移出了正常流程,并且隐藏了HttpServlet *
lilalinux

48

我会稍微改变实现:

首先,我创建一个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,MappingJackson2HttpMessageConverter以自动处理JSON序列化(如果将Jackson添加到类路径并添加@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);
}

我有500个日志:ay 28,2015 5:23:31 PM org.apache.cxf.interceptor.AbstractFaultChainInitiatorObserver onMessage严重:错误处理期间发生错误,放弃!org.apache.cxf.interceptor.Fault
剃刀

完美的解决方案,我只想补充一点,希望DTO是由Match其他对象组成的。
Marco Sulla

32

这是另一种方法。创建一个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


这种方法使您可以在堆栈跟踪中的任何位置终止执行,而不必返回“特殊值”,该值应指定要返回的HTTP状态代码。
穆罕默德·盖尔巴纳

21

如一些答案中所述,可以为要返回的每个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/


很不错的方法..不是一个简单的字符串的我更喜欢用的errorCode和消息字段返回一个JSON ..
伊斯梅尔Yavuz的

1
这应该是正确的答案,是带有自定义状态代码和消息的通用全局异常处理程序:D
Pedro Silva

10

我在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);
}

9

最简单的方法是扔一个 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;
    }

3
最佳答案:无需更改返回类型,也无需创建自己的异常。另外,如果需要,ResponseStatusException允许添加原因消息。
Migs

请注意,ResponseStatusException仅在春季版本5+中可用
Ethan Conner

2

使用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 / 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."}

0

您还可以throw new HttpMessageNotReadableException("error description")从Spring的默认错误处理中受益

但是,就像那些默认错误一样,不会设置任何响应主体。

在拒绝可能只是手工制作的请求时,我发现这些功能很有用,因为这可能会基于更深入的自定义验证及其标准掩盖了拒绝该请求这一事实,这可能表明恶意。

HTH,DTK


HttpMessageNotReadableException("error description")不推荐使用。
库巴Šimonovský19年

0

另一种方法是与一起使用@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;
  }
}

现在,您可以从任何控制器抛出异常,并且可以在通知类内部定义其他处理程序。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.