Using Spring Boot's ErrorController and Spring's ResponseEntityExceptionHandler correctly

后端 未结 2 1704
独厮守ぢ
独厮守ぢ 2021-02-02 00:33

The Question

When creating a controller in Spring Boot to handle all errors/exceptions in a custom way, including custom exceptions,

相关标签:
2条回答
  • 2021-02-02 00:51

    I will admit that I am not overly familiar with Spring's ErrorController, but looking at your specified goals I believe all of them can be achieved cleanly using Spring's @ControllerAdvice. The following is an example how I have been using it in my own applications:

    @ControllerAdvice
    public class ExceptionControllerAdvice {
    
        private static final String INCOMING_REQUEST_FAILED = "Incoming request failed:";
        private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionControllerAdvice.class);
        private final MessageSource source;
    
        public ExceptionControllerAdvice2(final MessageSource messageSource) {
            source = messageSource;
        }
    
        @ExceptionHandler(value = {CustomException.class})
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ResponseBody
        public ErrorMessage badRequest(final CustomException ex) {
            LOGGER.error(INCOMING_REQUEST_FAILED, ex);
            final String message = source.getMessage("exception.BAD_REQUEST", null, LocaleContextHolder.getLocale());
            return new ErrorMessage(HttpStatus.BAD_REQUEST.value(), message);
        }
    
        @ExceptionHandler(Throwable.class)
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        @ResponseBody
        public ErrorMessage internalServerError(final Exception ex) {
            LOGGER.error(INCOMING_REQUEST_FAILED, ex);
            final String message =
                    source.getMessage("exception.INTERNAL_SERVER_ERROR", null, LocaleContextHolder.getLocale());
            return new ErrorMessage(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
        }
    }
    
    0 讨论(0)
  • 2021-02-02 01:02

    Spring Boot application has a default configuration for error handling - ErrorMvcAutoConfiguration.

    What it basically does, if no additional configuration provided:

    • it creates default global error controller - BasicErrorController
    • it creates default 'error' static view 'Whitelabel Error Page'.

    BasicErrorController is wired to '/error' by default. If there is no customized 'error' view in the application, in case of an exception thrown from any controller, the user lands to /error whitelabel page, filled with information by BasicErrorController.

    If application has a controller implementing ErrorController it replaces BasicErrorController.

    If any exception occurs in error handling controller, it will go through Spring exception filter (see more details down below) and finally if nothing is found this exception will be handled by the underlying application container, e.g. Tomcat. The underlying container will handle the exception and show some error page/message depending on its implementation.

    There is an interesting piece of information in BasicErrorController javadoc:

    Basic global error Controller, rendering ErrorAttributes. More specific errors can be handled either using Spring MVC abstractions (e.g. @ExceptionHandler) or by adding servlet server error pages.

    BasicErrorController or ErrorController implementation is a global error handler. It can be used in conjunction with @ExceptionHandler.

    Here we come to ResponseEntityExceptionHandler

    A convenient base class for @ControllerAdvice classes that wish to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods. This base class provides an @ExceptionHandler method for handling internal Spring MVC exceptions.

    In other words, that means that ResponseEntityExceptionHandler is just a convenience class, which already contains Spring MVC exception handling. And we can use it as a base class for our custom class to handle controllers' exceptions. For our custom class to work, it must be annotated with @ControllerAdvice.

    Classes annotated with @ControllerAdvice can be used at the same time as the global error handler (BasicErrorController or ErrorController implementation). If our @ControllerAdvice annotated class (which can/or not extend ResponseEntityExceptionHandler) doesn't handle some exception, the exception goes to the global error handler.

    So far we looked at ErrorHandler controller and anything annotated with @ControllerAdvice. But it is much more complicated. I found a really valuable insight in the question - Setting Precedence of Multiple @ControllerAdvice @ExceptionHandlers.

    Edit:

    To keep it simple:

    1. First Spring searches for an exception handler (a method annotated with @ExceptionHandler) within @ControllerAdvice classes. See ExceptionHandlerExceptionResolver.
    2. Then it checks if the thrown exception is annotated with @ResponseStatus or derives from ResponseStatusException. See ResponseStatusExceptionResolver.
    3. Then it goes through Spring MVC exceptions' default handlers. See DefaultHandlerExceptionResolver.
    4. And at the end if nothing is found, the control is forwarded to the error page view with the global error handler behind it. This step is not executed if the exception comes from the error handler itself.
    5. If no error view is found (e.g. global error handler is disabled) or step 4 is skipped, then the exception is handled by the container.
    0 讨论(0)
提交回复
热议问题