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

Java 在Spring MVC@ResponseBy方法返回字符串时,如何响应HTTP 400错误?,java,spring,spring-mvc,http-error,Java,Spring,Spring Mvc,Http Error,我将SpringMVC用于一个简单的JSONAPI,使用基于@ResponseBody的方法,如下所示。(我已经有了一个直接生成JSON的服务层。) 问题是,在给定的场景中,用HTTP 400错误响应的最简单、最干净的方式是什么 我确实遇到过这样的方法: return new ResponseEntity(HttpStatus.BAD_REQUEST); …但是我不能在这里使用它,因为我的方法的返回类型是String,而不是ResponseEntity。这不一定是最简洁的方法,但在我看来非常干

我将SpringMVC用于一个简单的JSONAPI,使用基于
@ResponseBody
的方法,如下所示。(我已经有了一个直接生成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 doesnt work");
}
编辑如果使用Spring 3.1+,您可以在异常处理程序方法中使用@ResponseBody,否则使用
ModelAndView
或其他方法


像这样的方法应该行得通,我不确定是否有更简单的方法:

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

将您的返回类型更改为
ResponseEntity
,然后您可以使用下面的400

return new ResponseEntity<>(HttpStatus.BAD_REQUEST);


我想稍微改变一下实施方式:

首先,我创建一个
未知匹配异常

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UnknownMatchException extends RuntimeException {
    public UnknownMatchException(String matchId) {
        super("Unknown match: " + matchId);
    }
}
请注意的使用,它将由Spring的
响应StatusExceptionResolver
识别。如果抛出异常,它将创建具有相应响应状态的响应。(我还自由地将状态代码更改为
404-未找到
,我认为这更适合此用例,但如果您愿意,您可以坚持使用
HttpStatus.BAD_REQUEST
。)


接下来,我将更改
匹配服务
,使其具有以下签名:

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

最后,我将更新控制器并将其委托给Spring的
映射Jackson2HttpMessageConverter
,以自动处理JSON序列化(如果将Jackson添加到类路径,并将
@EnableWebMvc
添加到配置中,则默认情况下会添加JSON序列化,请参阅):


注意,将域对象与视图对象或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);
}

我认为这个线程实际上拥有最简单、最干净的解决方案,它不会牺牲Spring提供的JSON专业化工具:


我在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);
}
@RequestMapping(value=“/matches/{matchId}”,products=“application/json”)
@应答器
公共响应属性匹配(@PathVariable String matchId、@RequestBody String body、,
HttpServletRequest请求,HttpServletResponse响应){
产品p;
试一试{
p=service.getProduct(request.getProductId());
}捕获(例外情况除外){
返回新的响应属性(HttpStatus.BAD_请求);
}
返回新的响应状态(p,HttpStatus.OK);
}

这里有一种不同的方法。创建一个用
@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状态创建一个异常类。我不喜欢必须为每个项目的每个状态创建一个类的想法。这是我的想法

  • 创建接受HTTP状态的通用异常
  • 创建控制器通知异常处理程序
让我们进入代码

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

对于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);
JacksonMapper没有将其处理为“可转换”,因此我必须添加getter/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."}
你也可以从Spring的服务中获益

但是,与那些默认错误一样,不会设置响应主体

我发现,当拒绝只能合理地手工编制的请求时,这些方法很有用,可能表明恶意意图,因为它们掩盖了请求是基于更深入的自定义验证及其标准而被拒绝的事实

嗯,,
dtk

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

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

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

抱歉,这似乎不起作用。它在日志中生成带有长堆栈跟踪的HTTP 500“服务器错误”:
error org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver-调用@ExceptionHandler方法失败:public controller.TestController$MyError controller.TestController.handleException(controller.TestController$BadthineException)org.springframework.web.HttpMediaTypeNotAcceptableException:找不到可接受的表示形式
答案中是否缺少某些内容?此外,我还没有完全理解定义另一个自定义类型(MyError)的意义。有必要吗?我正在使用最新的Spring(3.2.2)。它适合我。我改用
javax.validation.ValidationException
。(Spring 3.1.4)这在服务和客户端之间有一个中间层的情况下非常有用,中间层有自己的错误处理能力。感谢您提供这个示例@Zuttyth这应该是公认的答案,因为它将异常处理代码移出了规范
// 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;
}
public class ErrorMessage {
    int code;

    String message;
}
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."}
    @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;
    }
@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;
  }
}