【问题标题】:Spring Boot @ExceptionHandler for Exception.class not invoking methods in ResponseEntityExceptionHandlerSpring Boot @ExceptionHandler for Exception.class 不调用 ResponseEntityExceptionHandler 中的方法
【发布时间】:2021-11-02 03:20:23
【问题描述】:

我已经完成了官方 Spring Boot 教程,包括 this 在内的一些 Stack Overflow 帖子,尝试过 @Ordered(Order=HIGHEST_PRECEDENCE),但除 ConstraintViolationException.class 之外的任何异常都由 DefaultHandlerExceptionResolver 处理

版本

  • Spring Boot - 2.5.3
  • 春天 - 5.3.9

问题

我希望MyRestController 中的任何异常都由我定义的ControllerAdvice 中的处理程序处理,以便我可以在发送到客户端之前操作响应对象。

@Validated
@RestController
@RequestMapping(value = "/myctx", produces = MediaType.APPLICATION_JSON_VALUE)
public class MyRestController {

    private static final Logger logger = LoggerFactory.getLogger(MyRestController);

    @RequestMapping(
            path = "/{element}",
            method = RequestMethod.POST,
            consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<ApiResponse> findAndSave(
            @Allowed(values = { "a1", "a2", "a3" }) @PathVariable String element,
            @ValidateMetadata @RequestBody Metadata metadata) {
                ...
                ...
    }
}

我定义的注解@Allowed@ValidateMetadata 工作正常

@ControllerAdvice
//@Order(Ordered.HIGHEST_PRECEDENCE)
public class ErrorControllerAdvice extends ResponseEntityExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(ErrorControllerAdvice.class);

    @ExceptionHandler({ ConstraintViolationException.class })
    public ResponseEntity<Object> handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) {
        ApiResponse apiResponse = ApiResponse.builder()
                .timestamp(new Date())
                .status(HttpStatus.BAD_REQUEST)
                .message(ex.getLocalizedMessage())
                .path(((ServletWebRequest) request).getRequest().getRequestURI())
                //.metadata(metadata)
                .build();

        return handleExceptionInternal(ex, apiResponse, new HttpHeaders(), apiResponse.getStatus(), request);
    }

    @ExceptionHandler({ Exception.class })
    public ResponseEntity<Object> handleAll(Exception ex, WebRequest request) throws Exception {
        ResponseEntity<Object> tmpResponse = super.handleException(ex, request);
        ResponseEntity<Object> response = new ResponseEntity<>("Response from catch-all exception handler", tmpResponse.getHeaders(), tmpResponse.getStatusCode());

        return response;
    }
}

此时,当我使用 POST 请求测试我的端点并注入一个 ContraintViolation 时,我的自定义错误响应会发送到客户端。但是,对于任何其他异常,ErrorControllerAdvice 中的 handleAll() 方法甚至不会被调用。例如,在测试代码中,我尝试发送 GET 而不是 POST 或将 URI 更改为包含 myct 而不是 myctx

为什么它不起作用?为什么在上述情况下会收到默认错误消息?

更新

我什至尝试从控制器方法中抛出 NullPointerException,但这也没有调用 handleAll() 方法。

【问题讨论】:

  • 我建议你删除extends ResponseEntityExceptionHandler这个并尝试!我知道你想要WebRequest。只需将它们注释掉并尝试一下

标签: spring spring-boot spring-mvc exception


【解决方案1】:

ResponseEntityExceptionHandler 处理了很多异常,所以handleAll 只会处理ResponseEntityExceptionHandler 中不包含的异常,你可以在ResponseEntityExceptionHandler 内部的handleException 方法中添加断点,看看是否正在捕获异常。您可以做的另一件事是在findAndSave 中抛出一个RuntimeException 并检查它是否由handleAll 处理

@ExceptionHandler({
            HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class,
            HttpMediaTypeNotAcceptableException.class,
            MissingPathVariableException.class,
            MissingServletRequestParameterException.class,
            ServletRequestBindingException.class,
            ConversionNotSupportedException.class,
            TypeMismatchException.class,
            HttpMessageNotReadableException.class,
            HttpMessageNotWritableException.class,
            MethodArgumentNotValidException.class,
            MissingServletRequestPartException.class,
            BindException.class,
            NoHandlerFoundException.class,
            AsyncRequestTimeoutException.class
        })

【讨论】:

    【解决方案2】:

    我能够自己解决它。我再次仔细阅读了此处发布的详细信息:Setting Precedence of Multiple @ControllerAdvice @ExceptionHandlers

    这就是我的@ControllerAdvice bean 现在的样子,让我能够为 Rest Controller 抛出的任何异常缝合自定义 ApiResponse 对象。

    @RestControllerAdvice
    public class ErrorControllerAdvice extends ResponseEntityExceptionHandler {
    
        private static final Logger logger = LoggerFactory.getLogger(ErrorControllerAdvice.class);
    
        @ExceptionHandler({ ConstraintViolationException.class })
        public ResponseEntity<Object> handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) {
    
            Metadata metadata = getRequestBody(request);
    
            ApiResponse apiResponse = new ApiResponse(); // details omitted
    
            return handleExceptionInternal(ex, apiResponse, new HttpHeaders(), apiResponse.getStatus(), request);
        }
    
        @ExceptionHandler({ Exception.class })
        public ResponseEntity<Object> handleAll(Exception ex, WebRequest request) {
            ResponseEntity<Object> tmpResponse = null;
            String message = null;
    
            try {
                tmpResponse = super.handleException(ex, request);
            } catch (Exception handlerEx) {
                logger.error(handlerEx.getMessage(), handlerEx);
            }
    
            HttpHeaders httpHeaders = tmpResponse == null ? new HttpHeaders() : tmpResponse.getHeaders();
            HttpStatus httpStatus = tmpResponse == null ? HttpStatus.INTERNAL_SERVER_ERROR : tmpResponse.getStatusCode();
    
            ApiResponse apiResponse = ApiResponse.builder()
                    .timestamp(new Date())
                    .status(httpStatus)
                    .message(ex.getLocalizedMessage()) // ex and handlerEx will have same content because of line 98 in ResponseEntityExceptionHandler
                    .path(((ServletWebRequest) request).getRequest().getRequestURI())
                    .build();
    
            return new ResponseEntity<>(apiResponse, httpHeaders, httpStatus);
        }
    }
    

    【讨论】:

    • 请说明与解决问题的原始建议类的确切区别。目前尚不清楚这是如何回答的。
    • 由于我是从ResourceEntityExceptionHandler 扩展而来的,所有异常都由该类中的handleException() 方法处理。这个方法 rethrows 如果它还没有被处理,那么这个异常。我的代码中缺少 try{}-catch{} 块,它可以处理我的应用程序中的 Rest Controller 引发的任何异常。
    • 正如@red 所指出的,我上面的评论措辞错误。这就是我真正的意思。 super.handlerException() 调用正在将所有异常发送到ResponseEntityExceptionHandler.handleException(),这会重新throws 任何不应该由它处理的异常。因此,代码段tmpResponse = super.handleException(ex, request); 实际上使我能够向 ReST 端点的调用者发送自定义异常,无论异常可能是什么。
    【解决方案3】:

    你的实现是错误的,handleException 方法只会处理那些在@ExceptionHandler 注解中定义的异常

    @ExceptionHandler({
                HttpRequestMethodNotSupportedException.class,
                HttpMediaTypeNotSupportedException.class,
                HttpMediaTypeNotAcceptableException.class,
                MissingPathVariableException.class,
                MissingServletRequestParameterException.class,
                ServletRequestBindingException.class,
                ConversionNotSupportedException.class,
                TypeMismatchException.class,
                HttpMessageNotReadableException.class,
                HttpMessageNotWritableException.class,
                MethodArgumentNotValidException.class,
                MissingServletRequestPartException.class,
                BindException.class,
                NoHandlerFoundException.class,
                AsyncRequestTimeoutException.class
            })
    

    所以,这些代码行总是会抛出异常。

    try {
                tmpResponse = super.handleException(ex, request);
            } catch (Exception handlerEx) {
                logger.error(handlerEx.getMessage(), handlerEx);
            }
    

    这个想法是错误的:

    由于我是从 ResourceEntityExceptionHandler 扩展而来,所有异常都由该类中的 handleException() 方法处理。

    【讨论】:

    • 我同意你的看法。您指出错误的语句应该是这样写的:“super.handlerException() call is sent all exceptions to ResponseEntityExceptionHandler.handleException() which re- throws any exception it't mean to handle” 所以,代码段 tmpResponse = super.handleException(ex, request); 实际上给了我能够向 ReST 端点的调用者发送自定义异常,无论异常可能是什么。
    猜你喜欢
    • 2021-12-08
    • 2017-03-29
    • 2021-03-24
    • 2021-05-27
    • 2019-08-01
    • 2018-11-16
    • 1970-01-01
    • 2012-04-12
    • 1970-01-01
    相关资源
    最近更新 更多