【问题标题】:Using Spring Boot's ErrorController and Spring's ResponseEntityExceptionHandler correctly正确使用 Spring Boot 的 ErrorController 和 Spring 的 ResponseEntityExceptionHandler
【发布时间】:2019-08-01 18:19:33
【问题描述】:

问题

在 Spring Boot 中创建控制器以自定义方式处理所有错误/异常时,包括自定义异常,应该首选哪种技术?

  1. 控制器是否应该实现 Spring Boot 的ErrorController

  2. 控制器是否应该扩展 Spring 的 ResponseEntityExceptionHandler

  3. 两者:单个控制器实现和扩展两个类,包括它们的两个功能?

  4. 两者:两个独立的控制器,一个实现ErrorController,另一个扩展ResponseEntityExceptionHandler

目标

这篇文章的原因是为了找到一种在 Spring Boot 中使用以下属性所有处理异常的方法:

  • 应捕获在处理请求期间出现在控制器/过滤器/拦截器中的所有Throwables。
  • 在捕获Throwable 的情况下,我们希望永远向客户端公开任何堆栈跟踪或其他实现细节(除非明确编码那样)。
  • 应该可以按其类分别处理所有发生的Throwables。对于任何其他未指定的类型,可以指定默认响应。 (我确信,@ExceptionHandler 可以做到这一点。但是ErrorController?)
  • 代码应尽可能简洁明了,没有丑陋的变通办法或 UB 来实现这一目标。

更多详情

我注意到两个控制器(参见上面的 1 和 2)可能包含返回 ResponseEntity 对象的方法,从而处理发生的异常并向客户端返回响应。那么理论上它们可以产生相同的结果吗?

有几个关于使用技术 1 和 2 的教程。但我没有发现任何文章考虑这两个选项、比较它们或一起使用它们,这引发了几个额外的问题:

  1. 是否应该一起考虑?

  2. 这两种提议的技术之间的主要区别是什么?有什么相似之处?

  3. 其中一个版本是否比另一个版本更强大?有没有什么可以做而另一个不能做的事情,反之亦然?

  4. 它们可以一起使用吗?是否有必要这样做?

  5. 如果它们一起使用,将如何处理异常?它是通过两个处理程序还是一个处理程序?如果是后者,是哪一个?

  6. 如果它们一起使用,并且在内部控制器(一个或另一个)在异常处理期间抛出异常,该异常将如何处理?它是否发送到另一个控制器?理论上异常会开始在控制器之间反弹或创建其他类型的不可恢复循环吗?

  7. 是否有任何关于 Spring Boot 如何在内部使用 Spring 的 ResponseEntityExceptionHandler 或它期望它如何在 Spring Boot 应用程序中使用的可信/官方文档?

  8. 如果只有ResponseEntityExceptionHandler就足够了,那为什么ErrorController还存在呢?

当查看 Spring 的 ResponseEntityExceptionHandler@ExceptionHandler 注解时,它似乎在分别处理不同类型的异常和使用更简洁的代码方面更强大。但是由于 Spring Boot 是建立在 Spring 之上的,这是否意味着:

  • 在使用Spring Boot时,我们应该实现ErrorController而不是扩展ResponseEntityExceptionHandler
  • ErrorController 可以做ResponseEntityExceptionHandler 可以做的所有事情,包括分别处理不同类型的异常吗?

编辑:相关:Spring @ControllerAdvice vs ErrorController

【问题讨论】:

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


    【解决方案1】:

    Spring Boot 应用程序具有用于错误处理的默认配置 - ErrorMvcAutoConfiguration

    如果没有提供额外的配置,它基本上是做什么的:

    • 它创建默认的全局错误控制器 - BasicErrorController
    • 它会创建默认的“错误”静态视图“Whitelabel 错误页面”。

    BasicErrorController 默认连接到“/error”。如果应用程序中没有自定义的“错误”视图,则在任何控制器抛出异常的情况下,用户会登陆 /error whitelabel 页面,由 BasicErrorController 填充信息。

    如果应用程序有一个控制器实现ErrorController替换 BasicErrorController

    如果错误处理控制器中发生任何异常,它将通过 Spring 异常过滤器(请参阅下面的更多详细信息),最后如果没有发现该异常将由底层应用程序容器处理,例如雄猫。底层容器将处理异常并根据其实现显示一些错误页面/消息。

    BasicErrorControllerjavadoc中有一条有趣的信息:

    基本的全局错误控制器,渲染ErrorAttributes。可以使用 Spring MVC 抽象(例如 @ExceptionHandler)或通过添加 servlet 服务器错误页面来处理更具体的错误。

    BasicErrorControllerErrorController 实现是一个全局错误处理程序。它可以与@ExceptionHandler 结合使用。

    我们来ResponseEntityExceptionHandler

    @ControllerAdvice 类的方便基类,这些类希望通过 @ExceptionHandler 方法在所有 @RequestMapping 方法中提供集中式异常处理。 这个基类提供了一个 @ExceptionHandler 方法来处理内部 Spring MVC 异常。

    换句话说,这意味着ResponseEntityExceptionHandler 只是一个便利类,它已经包含了Spring MVC 异常处理。我们可以将它用作自定义类的基类来处理控制器的异常。为了使我们的自定义类正常工作,它必须使用@ControllerAdvice 进行注释。

    带有@ControllerAdvice 注释的类可以与全局错误处理程序同时使用(BasicErrorControllerErrorController 实现)。如果我们的 @ControllerAdvice 注释类(可以/或不能扩展 ResponseEntityExceptionHandler)不处理某些异常,则异常会转到全局错误处理程序。

    到目前为止,我们查看了 ErrorHandler 控制器和任何带有 @ControllerAdvice 注释的内容。但这要复杂得多。 我在这个问题中发现了一个非常有价值的见解 - Setting Precedence of Multiple @ControllerAdvice @ExceptionHandlers

    编辑:

    为了简单起见:

    1. 首先 Spring 在 @ControllerAdvice 类中搜索异常处理程序(使用 @ExceptionHandler 注释的方法)。见ExceptionHandlerExceptionResolver
    2. 然后它检查抛出的异常是用@ResponseStatus 注释还是从ResponseStatusException 派生。见ResponseStatusExceptionResolver
    3. 然后它会通过 Spring MVC 异常的默认处理程序。见DefaultHandlerExceptionResolver
    4. 最后,如果没有找到,控件将被转发到错误页面视图,并在其后有全局错误处理程序。如果异常来自错误处理程序本身,则不会执行此步骤。
    5. 如果未找到错误视图(例如禁用全局错误处理程序)或跳过第 4 步,则由容器处理异常。

    【讨论】:

    • 这是否意味着当我扩展ResponseEntityExceptionHandler 并覆盖它的一些方法时,my 被覆盖的方法将自动用于 Spring MVC 异常处理?就像..如果我扩展这个类,Spring会检测到它而不使用它们的默认类吗?
    • 我有一个自定义实现 ErrorController 和一个自定义异常类型的 @ExceptionHandler。如果在我的任何控制器中抛出该自定义异常类型,默认情况下它会发送到ErrorController,而@ExceptionHandler 将被忽略。只有ErrorController 进一步抛出它,它才会被发送到@ExceptionHandler。这种行为和你描述的相反,怎么会?
    • 顺序如下:(1) 普通控制器抛出 -> (2) 我的 ErrorController 捕获并抛出 -> (3) DispatcherServlet 将其发送到 AbstractHandlerExceptionResolver,后者将其发送到我的@ExceptionHandler -> (4) 我的 @ExceptionHandler 捕获并解决它。
    • @anddero 谢谢,我的回复中发现与 DefaultHandlerExceptionResolver 执行顺序有关的错误。您描述的序列让我觉得导致ErrorController 的异常不是由您使用@ExceptionHandler 的任何方法处理的。这就是为什么第一个异常解析器搜索链直接将您引导至您的ErrorController。请在 HandlerExceptionResolverComposite.resolveException 处添加断点,看看它在那里停了多少次。
    • 好的,我想我现在明白了。 @ExceptionHandler 仅捕获从控制器抛出的异常。但在一种情况下,我的javax.servlet.Filter 实现在预处理期间抛出异常。在这种情况下,它会立即映射到 /error 并发送到我的 ErrorController 实现。但是因为ErrorController是一个控制器,如果进一步抛出异常,可以由@ExceptionHandler处理。
    【解决方案2】:

    我承认我对 Spring 的 ErrorController 并不太熟悉,但是看看您指定的目标,我相信所有这些目标都可以使用 Spring 的 @ControllerAdvice 干净利落地实现。以下是我在自己的应用程序中如何使用它的示例:

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

    【讨论】:

    • 谢谢,这似乎是一种非常按书本的方法,只使用@ExceptionHandlers。我可以理解并欣赏这个提议的简洁性,但我更感兴趣的是找出为什么 Spring Boot 决定引入 ErrorController 以及为什么在他们的教程中经常推广它。我的猜测是,这是另一种让他们的框架更抽象、更容易在 Spring 之上采用的方法,而无需太多编码和对底层实现的理解。我是根据 Spring Boot 作为框架的目标和来自 da-sha1 的上述答案提出这个建议的。
    猜你喜欢
    • 2019-03-26
    • 1970-01-01
    • 2017-07-23
    • 2021-06-26
    • 2021-11-02
    • 2016-08-04
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多