美团全新的这份Spring Boot异常处理笔记, 理论源码实战一应俱全!

251 阅读8分钟

Hello,今天给各位童鞋们分享SpringBoot异常处理,赶紧拿出小本子记下来吧

image.png

1、默认异常处理机制

默认情况下,SpringBoot 提供 /error 请求,来处理所有异常的。

浏览器客户端,请求头里的属性是Accept:text/html。表明它想要一个html类型的文本数据。因此返回的错误视图以HTML格式呈现,也就是响应一个“ whitelabel”错误视图。

image.png 如果是其他客户端,请求头里的属性是Accept:/,默认响应一个json数据 。

image.png

2、异常处理流程

介绍异常处理流程前,要先认识HandlerExceptionResolver:处理器异常解析器接口,可以将异常映射到相应的统一错误界面,从而显示用户友好的界面(而不是给用户看到具体的错误信息)

public interface HandlerExceptionResolver {

//解析处理异常

ModelAndView resolveException(

    HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);

}

在DispatcherServlet初始化时,已经把所有的HandlerExceptionResolver处理器异常解析器接口的实现类放到下面的集合里了

public class DispatcherServlet extends FrameworkServlet {

//异常解析器集合

private List<HandlerExceptionResolver> handlerExceptionResolvers;

}

image.png

从上图可以看出该集合里有DefaultErrorAttributes;还有HandlerExceptionResolverComposite处理器异常解析器组合,这里面包含了三个能真正处理异常的解析器,分别是ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver、DefaultHandlerExceptionResolver。下面会介绍他们几个分别用于处理什么异常。

阅读doDispatch()方法的源码可以看出,Spring MVC对整个doDispatch()方法用了嵌套的try-catch语句

  • 内层的try-catch用于捕获HandlerMapping进行映射查找HandlerExecutionChain以及HandlerAdapter执行具体Handler时的处理异常,并将异常传入到processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)方法中。外层try-catch用于捕获渲染视图时的异常。
  • 通过两层嵌套的try-catch,SpringMVC就能够捕获到三大组件在处理用户请求时的异常,通过这样的方法能够很方便的实现统一的异常处理。

image.png 从上面代码可以看出,当出现异常时会进入processHandlerException()方法进行异常视图的获取,处理完成后返回是ModelAndView对象。接下来不管视图是正常视图还是异常视图,只要ModelAndView不为空,均进入视图渲染流程。下面是如何进行进行异常视图的获取的代码。

image.png 遍历所有的处理器异常解析器handlerExceptionResolvers,看谁能够处理当前异常

image.png

  • DefaultErrorAttributes先来处理异常。把异常信息保存到request域,并且返回null,并不能真正解析。
  • HandlerExceptionResolverComposite会遍历它包含的三个异常解析器处理异常
  • ExceptionHandlerExceptionResolver处理器异常解析器支持@ControllerAdvice+@ExceptionHandler处理全局异常
  • ResponseStatusExceptionResolver处理器异常解析器支持@ResponseStatus+自定义异常
  • DefaultHandlerExceptionResolver 处理器异常解析器支持Spring底层的异常

当没有任何异常解析器能够处理异常,异常就会被抛出,最终Tomcat会发送 /error 请求,映射到底层的BasicErrorController进入默认的异常处理机制。

总结:

当发生异常时,会被catch。遍历所有的处理器异常解析器,看谁能够解析。如果你使用了@ControllerAdvice+@ExceptionHandler配置了全局异常处理,并指定了错误视图,那么该异常会被处理,然后进入视图渲染流程。如果该异常没能够被任何处理器异常解析器处理,就会抛出异常,由Tomcat发送/error请求,进入默认的异常处理机制,也就是开头说的,没有配置错误状态码页面,则返回默认我们常见的默认错误页。

访问的是不存在的路径,此时不会发生异常,经过处理器映射,处理器适配调用仍然返回的是空的ModelAndView,所以无法进行视图渲染。Tomcat仍会发送 /error请求,进入默认的异常处理机制。

3、默认的异常处理机制

要想弄懂错误处理原理,首先得看ErrorMvcAutoConfiguration:这是错误处理的自动配置类,给容器中添加了下面几个非常重要的组件。

  • ErrorPageCustomizer
  • BasicErrorController
  • DefaultErrorViewResolver
  • DefaultErrorAttributes

首先我们看ErrorPageCustomizer 组件,此组件是一个静态内部类,位于ErrorMvcAutoConfiguration内。它实现了ErrorPageRegistrar接口,该接口提供了可以用来注册ErrorPage的方法。官方将ErrorPage 描述为:简单的服务器独立的错误页面抽象,大致相当于web.xml中传统的元素。ErrorPage里包含了状态码、异常、和错误控制器映射路径(server.error.path=/error)。也就是说当发生了异常,而且所有的处理器异常解析器都处理不了该异常,Tomcat就会发送/error请求映射到BasicErrorController。

image.png 下面介绍的就是BasicErrorController。它里面有两个重要的方法,正好对象开头说的默认处理机制。方法一:如果是浏览器请求,则返回HTML响应数据text/html,方法二:如果是其他客户端请求,则返回JSON响应数据。

image.png 总结一下,BasicErrorController主要作用:

  • 处理默认/error 路径的请求
  • 调用DefaultErrorViewResolver进行错误视图解析,分为三种情况
  1. 模板引擎支持解析,就去 /templates/error/下寻找我们配置的 状态码错误页面,例如404.html 或4xx.html。
  2. 模板引擎找不到这个错误页面,就去静态资源文件夹【/resources/、/static/、/public/、/META-INF/resources/】下的error文件夹下寻找状态码错误页面。
  3. 静态资源文件夹下也找不到,则new ModelAndView("error", model)构造一个默认的错误视图【就是经常见到的 Whitelabel Error Page】。该默认的错误视图在ErrorMvcAutoConfiguration里已经注册到容器里了,并且它在容器中的id就是error。后面就会通过BeanNameViewResolver视图解析器,根据视图逻辑 error,作为组件id去容器中就可以找到默认的错误视图。

image.png

接下来就是介绍 DefaultErrorViewResolver,主要就是进行错误视图解析。如果发生错误,就会以HTTP的状态码 作为视图地址,找到真正的错误页面。但是注意,首先是精确查找具体的错误状态码页面,然后是按照4xx,5xx这种查找。

image.png

最后介绍DefaultErrorAttributes,里面存放了错误页面能够显示的数据。比如状态码、错误提示、异常消息等。

public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {

//帮我们在页面共享信息

@Override

    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,

            boolean includeStackTrace) {

        Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();

        errorAttributes.put("timestamp", new Date());

        addStatus(errorAttributes, requestAttributes);

        addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);

        addPath(errorAttributes, requestAttributes);

        return errorAttributes;

    }

}

总结:

  • 当发生了异常,而且所有的处理器异常解析器都处理不了该异常,ErrorPageCustomizer就会生效(定制错误的响应规则)。Tomcat就会发送/error请求,然后被HandlerMapping映射到BasicErrorController处理。
  • 解析错误视图:前提是配置了4xx.html、5xx.html错误状态码页面,去哪个状态码错误页面就由DefaultErrorViewResolver解析得到。如果没有配置错误状态码页面,就是默认的错误视图StaticView,它是位于ErrorMvcAutoConfiguration里的一个静态内部类,被自动注册到容器中。后面进行视图渲染的时候,就是StaticView里的render()方法构造了我们经常看到的默认错误页面【Whitelabel Error Page】。
  • 提取数据:页面能够获取什么数据是由 DefaultErrorViewResolver设置的。

4、自定义异常处理

1、自定义异常处理页

有模板引擎的情况下

  • error/状态码.html,就是将错误页面命名为状态码.html放在模板引擎文件夹里面的 error文件夹下;我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,不过优先寻找精确的状态码.html。
  • 没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找。依然要将错误页面放在error文件夹下。
  • 以上都没有找到错误页面,就是默认来到SpringBoot默认的错误提示页面。

错误页面能获取的信息DefaultErrorAttributes:

  • timestamp:时间戳
  • status:状态码
  • error:错误提示
  • exception:异常对象
  • message:异常消息
  • errors:JSR303数据校验的错误都在这里

2、@ControllerAdvice+@ExceptionHandler处理全局异常

底层是由 ExceptionHandlerExceptionResolver处理器异常解析器支持的

3、@ResponseStatus+自定义异常

底层是由 ResponseStatusExceptionResolver处理器异常解析器支持的,但是它解析完成后,调用了 **response.sendError(statusCode, resolvedReason);**由tomcat发送的/error请求,进入默认异常处理机制。

Spring底层的异常

如 参数类型转换异常;由DefaultHandlerExceptionResolver 处理器异常解析器支持的,处理框架底层的异常。但是它解析完成后,调用了 response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage()); 由tomcat发送的/error请求,进入默认处理机制。

扩展:【可不看】

自定义处理器异常解析器、错误视图解析器:

  • 实现 HandlerExceptionResolver接口自定义处理器异常解析器;可以作为默认的全局异常处理规则

image.png

  • 实现ErrorViewResolver自定义错误视图解析器

好啦,今天的文章就到这里,希望能帮助到屏幕前迷茫的你们