【问题标题】:Spring @ExceptionHandler returns wrong HttpStatus codeSpring @ExceptionHandler 返回错误的 HttpStatus 代码
【发布时间】:2017-12-30 23:24:32
【问题描述】:

TL;DR:当调用 HttpServletResponse.getStatusHttpStatus.valueOf(HttpServletResponse.getStatus)).name() 时,@ExceptionHandler 函数返回 200 OK 而不是 400 Bad RequestMissingServletParameterExceptionMissingServletParameterException 仅作为示例使用,其他异常也会发生。

你好,

我遇到的问题是我正在尝试将 Raygun(一个崩溃报告系统)与我们的 Java/Spring Boot 应用程序集成。我发现的最简单的方法是创建一个自定义异常处理程序,它将向用户显示错误消息并将异常传递给 Raygun。

最初,我尝试了这里建议的实现,并添加了我自己的 Raygun 实现 https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc

@ControllerAdvice
class GlobalDefaultExceptionHandler {
  public static final String DEFAULT_ERROR_VIEW = "error";

  private static ApiAccessToken accessToken = new ApiAccessToken();
  private static String databaseName = null;

  @ExceptionHandler(value = Exception.class)
  public ModelAndView
  defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
    // If the exception is annotated with @ResponseStatus rethrow it and let
    // the framework handle it
    if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
        throw e;
     }

    // Otherwise setup and send the user to a default error-view.
    ModelAndView mav = new ModelAndView();
    mav.addObject("exception", e);
    mav.addObject("url", req.getRequestURL());
    mav.setViewName(DEFAULT_ERROR_VIEW);

    // Display the error message to the user, and send the exception to Raygun along with any user details provided.
    RaygunClient client = new RaygunClient("<MyRaygunAPIKey>");

    if (accessToken.getUsername() != null && accessToken.getDatabaseName() != null) {
        ArrayList tags = new ArrayList<String>();
        tags.add("username: " + accessToken.getUsername());
        tags.add("database: " + accessToken.getDatabaseName());
        client.Send(e, tags);
        accessToken = null;
        return mav;

    } else if (databaseName != null) {
        ArrayList tags = new ArrayList<String>();
        tags.add("database: " + databaseName);
        client.Send(e, tags);
        databaseName = null;
        return mav;

    } else {
        client.Send(e);
        return mav;
    }
}

我遇到的问题是我们有公共和私有 API 端点。私有 API 端点用于我们的 iOS 应用程序,而公共 API 端点没有前端。它们旨在让企业能够集成到自己的系统中(PowerBI、Postman、自定义集成等)。因此没有可以使用 ModelAndView 重定向到的视图。

相反,我决定不使用 ModelAndView,而是返回一个已格式化为模仿 Spring 的默认 JSON 错误消息的字符串。

@ExceptionHandler(value = Exception.class)
public @ResponseBody String defaultErrorHandler(HttpServletRequest req, HttpServletResponse resp, Exception e) throws Exception {

    // Create a customised error message that imitates the Spring default Json error message
    StringBuilder sb = new StringBuilder("{ \n")
            .append("    \"timestamp\": ").append("\"").append(DateTime.now().toString()).append("\" \n")
            .append("    \"status\": ").append(resp.getStatus()).append(" \n")
            .append("    \"error\": ").append("\"").append(HttpStatus.valueOf(resp.getStatus()).name()).append("\" \n")
            .append("    \"exception\": ").append("\"").append(e.getClass().toString().substring(6)).append("\" \n")
            .append("    \"message\": ").append("\"").append(e.getMessage()).append("\" \n")
            .append("    \"path\": ").append("\"").append(req.getServletPath()).append("\" \n")
            .append("}");

    String errorMessage = String.format(sb.toString());

    // Display the error message to the user, and send the exception to Raygun along with any user details provided.
    RaygunClient client = new RaygunClient("<MyRaygunAPIKey>");

    if (accessToken.getUsername() != null && accessToken.getDatabaseName() != null) {
        ArrayList tags = new ArrayList<String>();
        tags.add("username: " + accessToken.getUsername());
        tags.add("database: " + accessToken.getDatabaseName());
        client.Send(e, tags);
        accessToken = null;
        return errorMessage;

    } else if (databaseName != null) {
        ArrayList tags = new ArrayList<String>();
        tags.add("database: " + databaseName);
        client.Send(e, tags);
        databaseName = null;
        return errorMessage;

    } else {
        client.Send(e);
        return errorMessage;
    }
}

唯一的问题是,当我故意引发异常时,它返回的 HTTP 状态为 200 OK,这显然是不正确的。

例如,defaultErrorHandler() 被注释掉(不向 Raygun 发送任何内容):

{
"timestamp": "2017-07-18T02:59:45.131+0000",
"status": 400,
"error": "Bad Request",
"exception": 
"org.springframework.web.bind.MissingServletRequestParameterException",
"message": "Required String parameter ‘foo’ is not present",
"path": "/api/foo/bar/v1"
}

这是没有注释掉的(将异常发送给 Raygun):

{ 
"timestamp": "2017-07-25T06:21:53.895Z" 
"status": 200 
"error": "OK" 
"exception": "org.springframework.web.bind.MissingServletRequestParameterException" 
"message": "Required String parameter 'foo' is not present" 
"path": "/api/foo/bar/V1" 
}

任何关于我做错了什么的帮助或建议将不胜感激。感谢您的宝贵时间。

【问题讨论】:

  • 你有没有尝试过类似@ResponseStatus(HttpStatus.CONFLICT)的方法声明?
  • 是的,不幸的是它仍然返回 200 OK 状态。
  • 尝试删除@ResponseBody并添加@ResponseStatus。如果它不起作用,请在调试模式下检查您的代码是否正在运行,因为可以通过其他方式处理异常。
  • 我不能使用@Response Status,因为我不知道 HttpStatus 是什么。我只是以MissingServletRequestParameterException 为例,而它可能是任何返回 400、403、405、415、500 等 HttpStatus 的异常。如果您检查我的答案,我现在可以正常工作了。不过感谢您的帮助。

标签: java spring http spring-boot exception-handling


【解决方案1】:

在您的控制器建议中尝试这种方式将异常类型映射到Http-Status,如下所示:

if (ex instanceof MyException)
{//just an example.
    return new ResponseEntity<>(e, HttpStatus.BAD_REQUEST);
}
else
{//all other unhandled exceptions
    return new ResponseEntity<>(e, HttpStatus.INTERNAL_SERVER_ERROR);
}

这里的 MyException 是您在运行时抛出的异常类型。假设我正在处理错误请求。

【讨论】:

  • 这行得通,但这不是我所追求的。我想要一个异常处理程序来处理所有抛出的异常。不过我现在已经想通了。
【解决方案2】:

我仍然不确定为什么在抛出异常时它会返回 200 OK 状态。但我已经意识到,我试图创建一个模仿 Spring 的默认 json 错误消息的字符串的做法过于复杂,根本没有必要。

将异常发送到 Raygun 后,我可以重新抛出异常并让框架像处理带有 @ResponseStatus 注释的任何异常一样处理它。

@ExceptionHandler(value = Exception.class)
public void defaultErrorHandler(Exception e) throws Exception {

    RaygunClient client = new RaygunClient("<MyRaygunAPIKey>");

    // If the exception is annotated with @ResponseStatus rethrow it and let
    // the framework handle it
    if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
        throw e;
    }

    // Otherwise send the exception Raygun and then rethrow it and let the framework handle it
    if (accessToken.getUsername() != null && accessToken.getDatabaseName() != null) {
        ArrayList tags = new ArrayList<String>();
        tags.add("username: " + accessToken.getUsername());
        tags.add("database: " + accessToken.getDatabaseName());
        client.Send(e, tags);
        accessToken = null;
        throw e;

    } else if (databaseName != null) {
        ArrayList tags = new ArrayList<String>();
        tags.add("database: " + databaseName);
        client.Send(e, tags);
        databaseName = null;
        throw e;

    } else {
        client.Send(e);
        throw e;
    }
}

一个基本的植入物看起来像这样:

@ExceptionHandler(value = Exception.class)
public void defaultErrorHandler(Exception e) throws Exception {

    RaygunClient client = new RaygunClient("<MyRaygunAPIKey>");

    // If the exception is annotated with @ResponseStatus rethrow it and let the framework handle it
    if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
        throw e;
    }
    // Otherwise send the exception Raygun and then rethrow it and let the framework handle it
    else {
        client.Send(e);
        throw e;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-24
    • 1970-01-01
    • 2011-12-04
    • 2014-07-03
    • 1970-01-01
    相关资源
    最近更新 更多