如何使用@ExceptionHandler 捕获 HTTP 405“不允许的方法”异常?

Posted

技术标签:

【中文标题】如何使用@ExceptionHandler 捕获 HTTP 405“不允许的方法”异常?【英文标题】:How to catch HTTP 405 "Method Not Allowed" exception using @ExceptionHandler? 【发布时间】:2021-02-13 22:33:56 【问题描述】:

我创建了一个 REST 应用程序并添加了一个类来处理异常:

@RestControllerAdvice(basePackages = "com.foxminded.university.api.controller")
public class ApiGlobalExceptionHandler extends ResponseEntityExceptionHandler 
    private static final String API_UNHANDLED_EXCEPTION = "REST API reached unhandled exception: %s";
    private static final HttpStatus internalServerError = HttpStatus.INTERNAL_SERVER_ERROR;

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> handle(Exception e) 
        ExceptionDetail exceptionDetail = new ExceptionDetail(
            String.format(API_UNHANDLED_EXCEPTION, e.getMessage()),
            internalServerError,
            ZonedDateTime.now(ZoneId.systemDefault()));
        return new ResponseEntity<Object>(exceptionDetail, internalServerError);
    

    @Override
    public ResponseEntity<Object> handleTypeMismatch(
        TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) 
        ExceptionDetail exceptionDetail = new ExceptionDetail(
            String.format(API_UNHANDLED_EXCEPTION, ex.getMessage()),
            internalServerError,
            ZonedDateTime.now(ZoneId.systemDefault()));
        return new ResponseEntity<Object>(exceptionDetail, internalServerError);
    

    @Override
    public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers,
        HttpStatus status, WebRequest request) 
        ExceptionDetail exceptionDetail = new ExceptionDetail(
            String.format(API_UNHANDLED_EXCEPTION, e.getMessage()),
            internalServerError,
            ZonedDateTime.now(ZoneId.systemDefault()),
            e.getBindingResult().getFieldErrors());
        return new ResponseEntity<Object>(exceptionDetail, internalServerError);
    

当我通过邮递员 http://localhost:8080/api/groups/jkjk 而不是 http://localhost:8080/api/groups 发送错误的发布请求时

它抛出了一个我在调试时无法捕获初始化的异常,无论是在 ApiGlobalExceptionHandler 类中还是在 ResponseEntityExceptionHandler 类中:


    "timestamp": 1604171144423,
    "status": 405,
    "error": "Method Not Allowed",
    "message": "",
    "path": "/api/groups/jkjk"

我可以捕获的所有其他异常。如何捕获此异常以添加自定义处理?

【问题讨论】:

【参考方案1】:

您只需在其签名中添加一个带有MethodNotAllowedException 的新方法。

@ExceptionHandler(value = MethodNotAllowedException.class)
public ResponseEntity<Object> handleMethodNotAllowedExceptionException(MethodNotAllowedException ex) 
    return buildResponseEntity(HttpStatus.METHOD_NOT_ALLOWED, null, null, ex.getMessage(), null);


private ResponseEntity<Object> buildResponseEntity(HttpStatus status, HttpHeaders headers, Integer internalCode, String message, List<Object> errors) 
    ResponseBase response = new ResponseBase() //A generic ResponseBase class
            .success(false)
            .message(message)
            .resultCode(internalCode != null ? internalCode : status.value())
            .errors(errors != null
                    ? errors.stream().filter(Objects::nonNull).map(Objects::toString).collect(Collectors.toList())
                    : null);
    
    return new ResponseEntity<>((Object) response, headers, status);

您可以随意自定义buildResponseEntity

更新

我重新审视了我的答案,因为它不符合您的要求。所以,它是这样的:

我发送一个接受GET 的方法的post 请求。这将触发 Request method 'POST' not supported,如下所示。

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.查找路径 /v1/user/profile/1 的处理程序方法 org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.Resolving 来自处理程序 [null] 的异常:org.springframework.web.HttpRequestMethodNotSupportedException:不支持请求方法“POST” org.springframework.beans.factory.support.DefaultListableBeanFactory.Returning 单例 bean 'ethGlobalExceptionHandler' 的缓存实例

在这种情况下,无需添加 @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class).

其实如果这样做的话,会抛出下面的错误(因为已经处理过了),

java.lang.IllegalStateException: Ambiguous @ExceptionHandler method mapped for [class org.springframework.web.HttpRequestMethodNotSupportedException]:....

所以,解决办法是:

@Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
        HttpHeaders headers, HttpStatus status, WebRequest request) 
    return buildResponseEntity(HttpStatus.METHOD_NOT_ALLOWED, headers, null, ex.getMessage(), Arrays.asList(""));

【讨论】:

但是为什么它没有被 @ExceptionHandler(Exception.class) 的句柄方法捕获?我预计它应该捕获 ResponseEntityExceptionHandler 方法未捕获的所有内容... 你是从 ResponseEntityExceptionHandler 扩展而来的,它只提供标准 Spring MVC 异常的处理。 我用下面的注解@ExceptionHandler (MethodNotAllowedException.class)向rest控制器添加了一个异常处理程序,但问题仍然存在。调试时光标不跳转到该方法,响应为默认。 我不确定我是否完全理解了你的所有观点,但这是我的疑问: 1. 你写了“我发送一个接受 GET 的方法的 post 请求。这将触发请求方法 'POST ' 不支持,如下所示。”它接受获取和发布。获取接收实体列表并发布以创建新实体。 2.你写了“事实上如果你这样做,会抛出以下错误(因为它已经被处理了)”。对于从 ResponseEntityExceptionHandler 扩展,我说的是 MethodNotAllowedException.class,而不是 HttpRequestMethodNotSupportedException.class。没有错误... 查看这个简短的描述evertpot.com/http/405-method-not-allowed【参考方案2】:

我在这里找到了解释Custom handling for 405 error with Spring Web MVC

上面写着The reason your @ExceptionHandler annotated method never catches your exception is because these ExceptionHandler annotated methods are invoked after a successful Spring controller handler mapping is found. However, your exception is raised before that.

并且解决方案不是从 ResponseEntityExceptionHandler 类扩展,而是从 DefaultHandlerExceptionResolver 扩展并覆盖它的 handleHttpRequestMethodNotSupported 方法。

【讨论】:

这将返回视图。在您的情况下,您期望的应该是 @ResponseBody 如果检查 DefaultHandlerExceptionResolver 的代码,handleHttpRequestMethodNotSupported 方法只返回空视图。那里的主要工作是"response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());"。因此,实际上它为响应配置错误,但不准备视图。你能具体说明一下,你为什么不喜欢这个答案,你怀疑什么? 但是,您不必将错误内容发送到响应正文吗? 正如我从我提到的帖子中了解到的(顺便说一句,它已经解决了问题),对于参数类型错误的帖子操作,不可能使用@ErrorHandling 注释重写默认错误处理全部...如果我错了,请纠正我。所以,也许还有另一种方法,但我没有找到任何其他解释,正如我所说,我无法通过你的方法捕捉到这个异常......也许我做错了......但是,当您正在为重写的 handleHttpRequestMethodNotSupported 方法调试代码,您是否能够在断点处停止返回语句? 只是为了说明问题,有一个映射到某个实体列表的休息控制器,并且有一个映射的 post 方法,当您发送诸如“site.com\entities”之类的 URI 的 post 请求时,它会创建新实体”。但是,当您为“site.com\entities\abc”发送帖子时,它会给出 405 状态的错误响应,我无法捕捉到。

以上是关于如何使用@ExceptionHandler 捕获 HTTP 405“不允许的方法”异常?的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot(十九)@ControllerAdvice+@ExceptionHandler全局捕获Controller异常

如何覆盖 $exceptionHandler 实现

SpringBoot全局异常捕获

统一异常处理@ExceptionHandler

当@Valid body 不满足并且@ExceptionHandler 命中时获取标题

springboot + @ControllerAdvice + @ExceptionHandler 实现全局异常拦截,不用