【问题标题】:Overriding the handling behavior for standard Spring MVC exceptions覆盖标准 Spring MVC 异常的处理行为
【发布时间】:2018-12-15 09:54:30
【问题描述】:

Spring Boot 似乎具有处理某些异常的默认行为。 我有一个休息控制器。如果我不在 @ControllerAdvice 带注释的 rest 控制器中处理 HttpRequestMethodNotSupportedException,应用程序会返回包含错误消息的默认 JSON 响应。

我不想替换此 JSON 响应,但我确实想在它发生时记录其他信息(例如记录某些请求者的 IP 地址)。

有没有办法通过 @ExceptionHandler 注释方法或其他机制来做到这一点?

【问题讨论】:

  • 您的意思是您想要自定义 JSON 响应,还是在发生这种情况时只需要额外的日志记录?

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


【解决方案1】:

Spring MVC 确实为您配置了一个异常处理程序。
默认情况下,DefaultHandlerExceptionResolver 使用类 javadoc 中的说明:

HandlerExceptionResolver 接口的默认实现 解决标准 Spring 异常并将它们转换为 对应的 HTTP 状态码。

这个异常解析器在常见的Spring中默认开启 org.springframework.web.servlet.DispatcherServlet.
这对 MVC 控制器来说是正确的。

但是对于 REST 控制器的异常处理程序(您的要求在这里),Spring 依赖于 ResponseEntityExceptionHandler 类。
第一个类有返回ModelAndViews 的方法,而第二个类有返回ReponseEntitys 的方法。

在这两种情况(MVC 和 REST 控制器)中,您都可以通过使用 @ControllerAdvice 注释您的类来定义自定义异常处理程序,但由于您的要求是 REST 控制器,所以让我们专注于这一点。

除了使用@ControllerAdvice 注释自定义异常处理程序之外,您还可以使用它来扩展基本异常处理程序类,例如ResponseEntityExceptionHandler 以覆盖某些行为。
ResponseEntityExceptionHandler 实现允许实际了解所有异常处理和映射。看handleException()方法,它是ResponseEntityExceptionHandler类的门面方法:

/**
 * Provides handling for standard Spring MVC exceptions.
 * @param ex the target exception
 * @param request the current request
 */
@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
    })
@Nullable
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
    HttpHeaders headers = new HttpHeaders();
    if (ex instanceof HttpRequestMethodNotSupportedException) {
        HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
        return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
    }
    else if (ex instanceof HttpMediaTypeNotSupportedException) {
        HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
        return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request);
    }
    else if (ex instanceof HttpMediaTypeNotAcceptableException) {
        HttpStatus status = HttpStatus.NOT_ACCEPTABLE;
        return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, headers, status, request);
    }
    else if (ex instanceof MissingPathVariableException) {
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return handleMissingPathVariable((MissingPathVariableException) ex, headers, status, request);
    }
    else if (ex instanceof MissingServletRequestParameterException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, headers, status, request);
    }
    else if (ex instanceof ServletRequestBindingException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleServletRequestBindingException((ServletRequestBindingException) ex, headers, status, request);
    }
    else if (ex instanceof ConversionNotSupportedException) {
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return handleConversionNotSupported((ConversionNotSupportedException) ex, headers, status, request);
    }
    else if (ex instanceof TypeMismatchException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleTypeMismatch((TypeMismatchException) ex, headers, status, request);
    }
    else if (ex instanceof HttpMessageNotReadableException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request);
    }
    else if (ex instanceof HttpMessageNotWritableException) {
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, headers, status, request);
    }
    else if (ex instanceof MethodArgumentNotValidException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleMethodArgumentNotValid((MethodArgumentNotValidException) ex, headers, status, request);
    }
    else if (ex instanceof MissingServletRequestPartException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleMissingServletRequestPart((MissingServletRequestPartException) ex, headers, status, request);
    }
    else if (ex instanceof BindException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleBindException((BindException) ex, headers, status, request);
    }
    else if (ex instanceof NoHandlerFoundException) {
        HttpStatus status = HttpStatus.NOT_FOUND;
        return handleNoHandlerFoundException((NoHandlerFoundException) ex, headers, status, request);
    }
    else if (ex instanceof AsyncRequestTimeoutException) {
        HttpStatus status = HttpStatus.SERVICE_UNAVAILABLE;
        return handleAsyncRequestTimeoutException(
                (AsyncRequestTimeoutException) ex, headers, status, request);
    }
    else {
        if (logger.isWarnEnabled()) {
            logger.warn("Unknown exception type: " + ex.getClass().getName());
        }
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return handleExceptionInternal(ex, null, headers, status, request);
    }
}   

所以问题是:如何覆盖特定异常的异常处理程序?
这种方法行不通:

@ExceptionHandler(value = { HttpRequestMethodNotSupportedException.class })
protected ResponseEntity<Object> handleConflict(HttpRequestMethodNotSupportedException ex, WebRequest request) {
   ...
}

因为在异常处理程序类中,Spring 不允许您为特定的Exception 子类定义多次映射。因此,不允许在自定义异常处理程序中添加此映射,因为 Spring 已经在 ResponseEntityExceptionHandler 类中定义了该异常的映射。
具体来说就是会阻止Spring容器启动成功。
你应该得到一个异常,例如:

引起:java.lang.IllegalStateException:为 [class org.springframework.web.HttpRequestMethodNotSupportedException] 映射的不明确的 @ExceptionHandler 方法:{protected org.springframework ...

为了简化客户端子类覆盖特定异常的实际处理/映射,Spring 在 ResponseEntityExceptionHandler 类的 protected 方法中实现了每个异常的逻辑,由它自己捕获和处理.
所以在你的情况下(覆盖HttpRequestMethodNotSupportedException的处理程序),只需覆盖你正在寻找的handleHttpRequestMethodNotSupported():

if (ex instanceof HttpRequestMethodNotSupportedException) {
    HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
    return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
}

例如这样:

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status,
            WebRequest request) {    
        // do your processing
         ...
        // go on (or no) executing the logic defined in the base class 
        return super.handleHttpRequestMethodNotSupported(ex, headers, status, request);
    }
}

【讨论】:

  • 覆盖方法适用于方法 handleHttpMessageNotReadable 但不适用于 ResponseEntityExceptionHandler 类中的其他方法(例如:handleNoHandlerFoundException)。知道为什么会发生这种情况吗?
【解决方案2】:

感谢 davidxxx 提供此答案和示例。他们帮了我很多。谢谢! 我是这样做的,它对我有用。这是一个例子:

    @ControllerAdvice
    public class AppExceptionHandler extends ResponseEntityExceptionHandler {
        @ExceptionHandler(ResourceNotFoundException.class)
        public ResponseEntity<?> resourceNotFoundException(final ResourceNotFoundException ex, final WebRequest request) {
            final ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
            return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
        }
    @ExceptionHandler(ResourceAlreadyExistsFoundException.class)
    public ResponseEntity<?> resourceAlreadyExistsFoundException(final ResourceAlreadyExistsFoundException ex, final WebRequest request) {
        final ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    ResponseEntity<?> onConstraintValidationException(
            ConstraintViolationException e) {
        List<ErrorDetails> errors = new ArrayList<>();
        for (ConstraintViolation violation : e.getConstraintViolations()) {
            errors.add(
                    new ErrorDetails(new Date(), violation.getPropertyPath().toString(), violation.getMessage()));
        }
        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        ArrayList<ErrorDetails> errors = new ArrayList<>();
        for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
            errors.add(
                    new ErrorDetails(new Date(), fieldError.getField(), fieldError.getDefaultMessage()));
        }
        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> globleExcpetionHandler(final Exception ex, final WebRequest request) {
        final ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-28
    • 2018-12-29
    • 2016-06-04
    相关资源
    最近更新 更多