正确使用Spring Boot的ErrorController和Spring的ResponsueEntityExceptionHandler

问题

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

  1. 控制器应该实现弹簧靴的吗?ErrorController

  2. 控制器是否应该扩展弹簧的?ResponseEntityExceptionHandler

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

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

目标

这篇文章的原因是找到一种在Spring Boot中处理异常的方法,其中包含以下所有属性:

  • 在处理请求期间,控制器/过滤器/拦截器中发生的所有 s 都应被捕获。Throwable
  • 在捕获 的情况下,我们不希望向客户端公开任何堆栈跟踪或其他实现细节(除非以这种方式显式编码)。Throwable
  • 应该可以按其类单独处理所有发生的 s。对于任何其他未指定的类型,可以指定默认响应。(我可以肯定的是,这是可能的。但是?Throwable@ExceptionHandlerErrorController
  • 代码应该尽可能干净和明确,没有丑陋的解决方法或UB来实现这个目标。

更多详情

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

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

  1. 它们是否应该一起考虑?

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

  3. 一个是另一个更强大的版本吗?有没有一个人能做而另一个人做不到的事情,反之亦然?

  4. 它们可以一起使用吗?在有些情况下,这样做是必要的吗?

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

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

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

  8. 如果单独一个人已经足够了,那么为什么存在呢?ResponseEntityExceptionHandlerErrorController

当将Spring与注释一起查看时,它似乎在单独处理不同类型的异常和使用更干净的代码方面更强大。但是,因为Spring Boot是建立在Spring之上的,这是否意味着:ResponseEntityExceptionHandler@ExceptionHandler

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

编辑:相关:弹簧@ControllerAdvice与错误控制器


答案 1

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

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

  • 它创建默认的全局错误控制器 - 基本错误控制器
  • 它创建默认的“错误”静态视图“白标错误页面”。

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

如果应用程序具有实现的控制器,它将替换ErrorControllerBasicErrorController

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

javadoc中有一个有趣的信息:BasicErrorController

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

BasicErrorController或实现是全局错误处理程序。它可以与@ExceptionHandler结合使用。ErrorController

在这里,我们来响应实体异常处理程序

一个方便的基类,适用于希望通过@ExceptionHandler方法跨所有@RequestMapping方法提供集中式异常处理的@ControllerAdvice类。此基类提供了用于处理内部 Spring MVC 异常的@ExceptionHandler方法。

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

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

到目前为止,我们查看了控制器和任何用 .但它要复杂得多。我在这个问题中发现了一个非常有价值的见解 - 设置多个@ControllerAdvice @ExceptionHandlers的优先级ErrorHandler@ControllerAdvice

编辑:

为了简单起见:

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

答案 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);
    }
}

推荐