简体   繁体   English

覆盖标准 Spring MVC 异常的处理行为

[英]Overriding the handling behavior for standard Spring MVC exceptions

Spring Boot seems to have a default behavior for handling certain exceptions. Spring Boot 似乎有处理某些异常的默认行为。 I have a rest controller.我有一个休息控制器。 If I do not handle HttpRequestMethodNotSupportedException in the @ControllerAdvice annotated rest controller, the application returns a default JSON response containing the error message.如果我不在@ControllerAdvice注释的休息控制器中处理HttpRequestMethodNotSupportedException ,应用程序将返回包含错误消息的默认 JSON 响应。

I do not want to replace this JSON response, but I do want to log additional information (eg log certain requestor's IP address) when it happens.我不想替换这个 JSON 响应,但我想在它发生时记录附加信息(例如记录某些请求者的 IP 地址)。

Is there a way to do this with an @ExceptionHandler annotated method, or other mechanism?有没有办法用@ExceptionHandler注释方法或其他机制来做到这一点?

Spring MVC configures indeed an exception handler for you. Spring MVC 确实为您配置了一个异常处理程序。
By default, DefaultHandlerExceptionResolver is used as stated in the class javadoc :默认情况下, DefaultHandlerExceptionResolver按照类 javadoc 中的说明使用:

Default implementation of the HandlerExceptionResolver interface that resolves standard Spring exceptions and translates them to corresponding HTTP status codes. HandlerExceptionResolver接口的默认实现,用于解析标准 Spring 异常并将它们转换为相应的 HTTP 状态代码。

This exception resolver is enabled by default in the common Spring org.springframework.web.servlet.DispatcherServlet .默认情况下,在公共 Spring org.springframework.web.servlet.DispatcherServlet启用此异常解析器。
That is right for MVC controllers.这适用于 MVC 控制器。

But for exception handlers for REST controllers (your requirement here), Spring rely on the ResponseEntityExceptionHandler class.但是对于 REST 控制器的异常处理程序(这里是您的要求),Spring 依赖于ResponseEntityExceptionHandler类。
The first class have methods that return ModelAndView s while the second class have methods that return ReponseEntity s.第一个类具有返回ModelAndView的方法,而第二个类具有返回ReponseEntity的方法。

You can define a custom exception handler by annotating your class with @ControllerAdvice in both cases (MVC and REST controllers) but since your requirement is for REST controllers, let's focus on that.在两种情况下(MVC 和 REST 控制器),您都可以通过使用@ControllerAdvice注释您的类来定义自定义异常处理程序,但由于您的要求是针对 REST 控制器,让我们专注于这一点。

Besides annotating a custom exception handler with @ControllerAdvice , you can also make that to extend a base exception handler class such as ResponseEntityExceptionHandler to override some behaviors.除了使用@ControllerAdvice注释自定义异常处理程序之外,您还可以通过扩展基本异常处理程序类(例如ResponseEntityExceptionHandler来覆盖某些行为。
ResponseEntityExceptionHandler implementations allows to know all the exceptions actually handled and mapped. ResponseEntityExceptionHandler实现允许知道所有实际处理和映射的异常。 Look at the handleException() method that is the facade method of the ResponseEntityExceptionHandler class :查看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);
    }
}   

So the question is : how to override the exception handler for a specific exception ?所以问题是:如何覆盖特定异常的异常处理程序?
This approach cannot work :这种方法行不通:

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

Because inside the exception handler class, Spring doesn't let you to define more than a single time a mapping for a specific Exception subclass.因为在异常处理程序类中,Spring 不允许您多次定义特定Exception子类的映射。 So adding this mapping in your custom exception handler is not allowed because Spring already defines a mapping for that exception in the ResponseEntityExceptionHandler class.因此,不允许在自定义异常处理程序中添加此映射,因为 Spring 已经在ResponseEntityExceptionHandler类中为该异常定义了一个映射。
Concretely, it will prevent the Spring container from starting successfully.具体来说,它会阻止 Spring 容器成功启动。
You should get an exception such as :你应该得到一个异常,例如:

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

To ease client subclasses to override the actual handling/mapping for a specific exception, Spring implemented the logic of each exception caught and handled by itself in a protected method of the ResponseEntityExceptionHandler class.为了简化客户端子类以覆盖特定异常的实际处理/映射,Spring 在ResponseEntityExceptionHandler类的protected方法中实现了由自身捕获和处理的每个异常的逻辑。
So in your case (overriding the handler of HttpRequestMethodNotSupportedException ), just override handleHttpRequestMethodNotSupported() that is what you are looking for :因此,在您的情况下(覆盖HttpRequestMethodNotSupportedException的处理程序),只需覆盖您正在寻找的handleHttpRequestMethodNotSupported()

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

For example in this way :例如以这种方式:

@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);
    }
}

Thanks davidxxx for this answer and examples.感谢 davidxxx 提供此答案和示例。 They helped me a lot.他们帮了我很多。 thank you!谢谢你! I did it this way and it worked for me.我是这样做的,它对我有用。 Here is an example:下面是一个例子:

    @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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM