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

时间:2019-03-11 12:24:20

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

问题

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

  1. 控制器应该实现Spring Boot的ErrorController吗?

  2. 控制器应该扩展Spring的ResponseEntityExceptionHandler吗?

  3. 都是:一个控制器实现并扩展两个类(包括它们的功能)吗?

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

目标

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

  • 在处理请求期间出现在控制器/过滤器/拦截器中的所有Throwable
  • 在捕获Throwable的情况下,我们不想永远向客户端公开任何堆栈跟踪或其他实现细节(除非明确地以这种方式编码。)
  • 应该有可能按其类分别处理所有发生的Throwable。对于任何其他未指定的类型,可以指定默认响应。 (我肯定知道,@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

2 个答案:

答案 0 :(得分:3)

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

如果没有提供其他配置,它的基本作用是

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

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

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

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

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

  

基本全局错误控制器,呈现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步,则该异常由容器处理。

答案 1 :(得分:0)

我承认我对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);
    }
}