【问题标题】:Spring boot Rest responding with empty body for exceptions other than the ones overridden in my @ControllerAdviceSpring boot Rest 以空主体响应我的 @ControllerAdvice 中覆盖的异常以外的异常
【发布时间】:2023-03-25 07:29:01
【问题描述】:

我有一个扩展 ResponseEntityExceptionHandler 的 @ControllerAdvice,试图让我控制 API 调用工作流中引发的任何异常的标准响应。

没有控制器的建议。我得到了 spring 生成的基于 HTML 的通用响应,并带有正确的响应标头。但是当我添加我的@ControllerAdvice 时,Spring 不会以通用错误主体响应。正文为空,响应头正确

@Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
        HttpHeaders headers, HttpStatus status, WebRequest request) {

        String erroMessage = "Required Parameter: '"+ex.getParameterName()+"' was not available in the request.";
        TrsApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, erroMessage, ex, ApiErrorCode.INVALID_REQUEST);
        return buildResponseEntity(apiError);
}

因此,现在,如果请求中缺少必需的参数,流程会完美地触发我的重写实现,并使用描述错误的 JSON 有效负载进行响应。但是,如果出现任何其他异常,例如 HttpMediaTypeNotAcceptableException,spring 会以空主体响应。

在我添加我的建议之前,spring 以通用错误响应进行响应。我是 Spring Boot 生态系统的新手。需要帮助了解这是否是预期行为,是否有更好的方法来实现集中式错误处理。

【问题讨论】:

    标签: java spring spring-boot controller-advice


    【解决方案1】:

    我想当 ControllerAdvice 类扩展 ResponeEntityExceptionHandler 时,我找到了吞下身体的解决方案。就我而言,设置如下所示:

    @ControllerAdvice
    @Slf4j
    class GlobalExceptionHandlers extends ResponseEntityExceptionHandler {
    
        @Override
        protected ResponseEntity<Object> handleMethodArgumentNotValid(
                                          MethodArgumentNotValidException exception,
                                          HttpHeaders headers,
                                          HttpStatus status,
                                          WebRequest request) {
            // logic that creates apiError object (object with status, message, errorCode, etc)
            //...
            return handleExceptionInternal(exception, apiError, headers, status, request);
        }
    

    对于MethodArgumentNotValidException 类的例外情况,这就像一个魅力。但它破坏了ResponseEntityExceptionHandler 处理的所有其他异常,并为它们返回了空的响应正文。

    但修复很简单,只需从 ResponseEntityExceptionHandler 覆盖 handleExceptionInternal

    @ControllerAdvice
    @Slf4j
    class GlobalExceptionHandlers extends ResponseEntityExceptionHandler {
    
        /// ... code from previous snippet
    
        @Override
        protected ResponseEntity<Object> handleExceptionInternal(
                                          Exception exception, 
                                          Object body, 
                                          HttpHeaders headers, 
                                          HttpStatus status, 
                                          WebRequest request) {
            // for all exceptions that are not overriden, the body is null, so we can
            // just provide new body based on error message and call super method
            var apiError = Objects.isNull(body) 
                    ? new ApiError(status, exception.getMessage()) // <-- 
                    : body;
            return super.handleExceptionInternal(exception, apiError, headers, status, request);
        }
    }
    

    【讨论】:

      【解决方案2】:

      这是预期行为。查看类 ResponseEntityExceptionHandler 的源代码。

      @ExceptionHandler({
                  org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException.class,
                  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
              })
          public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
      

      所有这些异常都是在没有响应正文的情况下处理的。 调用了一个通用方法:

      //second parameter is body which is null
      handleExceptionInternal(ex, null, headers, status, request)
      

      如果您需要以不同方式处理特定异常,请覆盖它们,例如我想为 HttpMessageNotReadableException

      发送自定义响应
       @Override
          protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
              HttpHeaders headers, HttpStatus status, WebRequest request)
          {
              logger.error("handleHttpMessageNotReadable()", ex);
              ValidationErrors validationErrors = null;
              if (ex.getRootCause() instanceof InvalidFormatException) {
                  InvalidFormatException jacksonDataBindInvalidFormatException = (InvalidFormatException) ex.getRootCause();
                  validationErrors = new ValidationErrors(jacksonDataBindInvalidFormatException.getOriginalMessage());
              }
              headers.add("X-Validation-Failure", "Request validation failed !");
              return handleExceptionInternal(ex, validationErrors, headers, status, request);
          }
      

      【讨论】:

      • 当我没有我的建议时,spring 会在响应正文中发送错误堆栈。除了我覆盖的方法之外,我希望 spring 以相同的方式运行。例如:我想提供自己的实现handleMethodArgumentNotValid,但是,我需要spring来处理handleHttpMediaTypeNotAcceptable
      • 不扩展 ResponseEntityExceptionHandler 会发生什么?你能检查一下自定义异常和 Spring MVC 异常 handleHttpMediaTypeNotAcceptable
      • 因此,当我不从 ResponseEntityExceptionHandler 扩展时,spring 开始使用适当的响应主体处理 handleHttpMediaTypeNotAcceptable
      • code

        Whitelabel 错误页面

        此应用程序没有 /error 的显式映射,因此您将此视为后备。

        2019 年 6 月 11 日星期二 09:44:35 EDT
        出现意外错误(type=Not Acceptable, status=406)。
        找不到可接受的表示形式
        org.springframework.web.HttpMediaTypeNotAcceptableException: 找不到可接受的表示形式code
      • 好的,然后不要从 responseEntity 扩展......而只需添加您的自定义 @ExceptionHandler... @ExceptionHandler(RuntimeException.class) public ResponseEntity handleRuntimeException(RuntimeException ex, WebRequest request)
      【解决方案3】:

      使用@ControllerAdvice 后,您需要定义通用异常结构。

      @ResponseBody
      @ExceptionHandler(Exception.class)
      @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
      public ErrorResponse generationExceptionHandler(Exception e){
          log.info("Responding INTERNAL SERVER ERROR Exception");
          return new ErrorResponse(ServiceException.getSystemError());
      }
      

      【讨论】:

      • 我有以下内容:@ResponseBody @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) protected ResponseEntity handleDefaultException(Exception ex) { logger.error("", ex); ApiError apiError = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage(), ex); } 返回 buildResponseEntity(apiError);因此,现在如果我创建一个请求而不保留必填字段,则对我的客户端的响应将带有空正文和 404 标头。甚至我在方法中的断点也没有被触发。
      • 你用 ResponseEntityExceptionHandler 扩展你的类了吗??
      • 并覆盖此方法 protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request)
      • 我已经扩展了我在描述中提到的课程。我的问题是,如果我覆盖了 handleMethodArgumentNotValid 那么它需要我的实现。但是,如果我不这样做,那么它不会将发送 spring 错误堆栈的默认实现作为响应。
      猜你喜欢
      相关资源
      最近更新 更多
      热门标签