Java 如何在返回字符串的 Spring MVC @ResponseBody 方法中响应 HTTP 400 错误?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16232833/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-16 05:55:11  来源:igfitidea点击:

How to respond with HTTP 400 error in a Spring MVC @ResponseBody method returning String?

javaspringspring-mvchttp-error

提问by Jonik

I'm using Spring MVC for a simple JSON API, with @ResponseBodybased approach like the following. (I already have a service layer producing JSON directly.)

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

Question is, in the given scenario, what is the simplest, cleanest way to respond with a HTTP 400 error?

问题是,在给定的场景中,响应 HTTP 400 错误的最简单、最干净的方法是什么?

I did come across approaches like:

我确实遇到过类似的方法:

return new ResponseEntity(HttpStatus.BAD_REQUEST);

...but I can't use it here since my method's return type is String, not ResponseEntity.

...但我不能在这里使用它,因为我的方法的返回类型是字符串,而不是 ResponseEntity。

采纳答案by Bassem Reda Zohdy

change your return type to ResponseEntity<>, then you can use below for 400

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

return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

and for correct request

和正确的请求

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

UPDATE 1

更新 1

after spring 4.1 there are helper methods in ResponseEntity could be used as

在 spring 4.1 之后, ResponseEntity 中的辅助方法可以用作

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

and

return ResponseEntity.ok(json);

回答by Zutty

Not necessarily the most compact way of doing this, but quite clean IMO

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

Edit you can use @ResponseBody in the exception handler method if using Spring 3.1+, otherwise use a ModelAndViewor something.

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

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

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

回答by stacker

Something like this should work, I'm not sure whether or not there is a simpler way:

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

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

回答by matsev

I would change the implementation slightly:

我会稍微改变一下实现:

First, I create a UnknownMatchException:

首先,我创建一个UnknownMatchException

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

Note the use of @ResponseStatus, which will be recognized by Spring's ResponseStatusExceptionResolver. If the exception is thrown, it will create a response with the corresponding response status. (I also took the liberty of changing the status code to 404 - Not Foundwhich I find more appropriate for this use case, but you can stick to HttpStatus.BAD_REQUESTif you like.)

注意@ResponseStatus的使用,Spring 的ResponseStatusExceptionResolver. 如果抛出异常,它将创建一个具有相应响应状态的响应。(我还冒昧地更改了404 - Not Found我认为更适合此用例的状态代码,但HttpStatus.BAD_REQUEST如果您愿意,可以坚持使用。)



Next, I would change the MatchServiceto have the following signature:

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

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


Finally, I would update the controller and delegate to Spring's MappingHymanson2HttpMessageConverterto handle the JSON serialization automatically (it is added by default if you add Hymanson to the classpath and add either @EnableWebMvcor <mvc:annotation-driven />to your config, see the reference docs):

最后,我将更新控制器并委托给 SpringMappingHymanson2HttpMessageConverter以自动处理 JSON 序列化(如果您将 Hymanson 添加到类路径并添加@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);
}


Note, it is very common to separate the domain objects from the view objects or DTO objects. This can easily be achieved by adding a small DTO factory that returns the serializable JSON object:

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

回答by Ryan S

I think this thread actually has the easiest, cleanest solution, that does not sacrifice the JSON martialing tools that Spring provides:

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

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

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

回答by Aamir Faried

I m using this in my spring boot application

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

回答by danidemi

Here's a different approach. Create a custom Exceptionannotated with @ResponseStatus, like the following one.

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

@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Not Found")
public class NotFoundException extends Exception {

    public NotFoundException() {
    }
}

And throw it when needed.

并在需要时扔掉它。

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

Check out the Spring documentation here: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-annotated-exceptions.

在此处查看 Spring 文档:http: //docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-annotated-exceptions

回答by Norris

As mentioned in some answers, there is the ability to create an exception class for each HTTP status that you want to return. I don't like the idea of having to create a class per status for each project. Here is what I came up with instead.

正如一些答案中提到的,可以为您想要返回的每个 HTTP 状态创建一个异常类。我不喜欢必须为每个项目为每个状态创建一个类的想法。这是我想出来的。

  • Create a generic exception that accepts an HTTP status
  • Create an Controller Advice exception handler
  • 创建一个接受 HTTP 状态的通用异常
  • 创建 Controller Advice 异常处理程序

Let's get to the code

让我们来看看代码

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

Then I create a controller advice class

然后我创建了一个控制器建议类

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

To use it

使用它

throw new ResourceException(HttpStatus.BAD_REQUEST, "My message");

http://javaninja.net/2016/06/throwing-exceptions-messages-spring-mvc-controller/

http://javaninja.net/2016/06/throwing-exceptions-messages-spring-mvc-controller/

回答by EpicPandaForce

With Spring Boot, I'm not entirely sure why this was necessary (I got the /errorfallback even though @ResponseBodywas defined on an @ExceptionHandler), but the following in itself did not work:

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

It still threw an exception, apparently because no producible media types were defined as a request attribute:

它仍然抛出异常,显然是因为没有可生产的媒体类型被定义为请求属性:

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

So I added them.

所以我添加了它们。

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

And this got me through to have a "supported compatible media type", but then it still didn't work, because my ErrorMessagewas faulty:

这让我获得了“支持的兼容媒体类型”,但仍然无法正常工作,因为我的ErrorMessage错误:

public class ErrorMessage {
    int code;

    String message;
}

HymansonMapper did not handle it as "convertable", so I had to add getters/setters, and I also added @JsonPropertyannotation

HymansonMapper 没有将其作为“可转换”处理,因此我不得不添加 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;
    }
}

Then I received my message as intended

然后我按预期收到了我的消息

{"code":400,"message":"An \"url\" parameter must be defined."}

回答by dtk

You also could just throw new HttpMessageNotReadableException("error description")to benefit from Spring's default error handling.

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

However, just as is the case with those default errors, no response body will be set.

但是,就像这些默认错误的情况一样,不会设置响应正文。

I find these useful when rejecting requests that could reasonably only have been handcrafted, potentially indicating a malevolent intent, since they obscure the fact that the request was rejected based on a deeper, custom validation and its criteria.

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

Hth, dtk

hth, dtk