1、错误页

1. 雷神

1.1 官方错误处理逻辑

1.1.1 默认规则

官网文档

image.png

翻译过来:

  • 默认情况下,Spring Boot提供/error处理所有错误的映射
  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
  • 要对其进行自定义,添加**View**解析为
  • 要完全替换默认行为,可以实现 ErrorController并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。
  • error/下的4xx,5xx页面会被自动解析;
    • image.png

1.1.2 原理:BasicErrorController类。

1.1.3 注意:响应码.html优先级高于4xx.html

image.png

1.2 定制错误处理逻辑

如果在 /resources/public/error、/resources/templates/error里自定义404和500等会被默认配置。
5xx表示所有以5开头的状态信息,都能和这个匹配
image.png

  • 自定义错误页
    • error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
  • @ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的
  • @ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error
  • Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
    • response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
    • image.png
  • 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则
    • image.png
  • ErrorViewResolver 实现自定义处理异常;
    • response.sendError 。error请求就会转给controller
    • 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
    • basicErrorController 要去的页面地址是 ErrorViewResolver;

2. 自定义错误处理逻辑(书)

有三种深度定制:自定义Error数据、自定义Error视图、完全自定义

1.自定义Error数据:替换ErrorAttribute

数据共有5种:时间戳、状态码、错误信息、错误原因、路径

Error数据一共有5条,分别是timestamp、status、error、message、path
提供一个ErrorAttribute类,那么框架就会用我们自己提供的错误数据类。

  1. 自定义数据错误类:添加cutommsg属性,移除error属性

    1. @Component
    2. public class MyErrorAttribute extends DefaultErrorAttributes {
    3. @Override
    4. public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
    5. Map<String,Object>errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace);
    6. errorAttributes.put("custommsg","请求出错啦");
    7. errorAttributes.remove("error");
    8. System.err.println(errorAttributes);
    9. return errorAttributes;
    10. }
    11. }

    image.png

  2. 写前端页面

image.png

  1. 进行错误访问,查看运行结果

image.png

原理:

所有的一切由ErrorMvcAutoConfiguration总配置。
BasicErrorController的errorHtml()和error()通过getErrorAttribute()获取Error信息。
最终会调用DefaultErrorAttributes 类的 getErrorAttributes()。因为只需重写 getErrorAttributes()
image.png
image.png
当系统没有提供ErrorAttributes的时候,才会采用DefaultErrorAttributes。
因此只需自己提供一个 ErrorAttributes 即可,而 DefaultErrorAttributes 是 ErrorAttributes 的子类,因此只需要继承 DefaultErrorAttributes 即可。

2. 自定义错误视图:替换ErrorViewResolver

实现 ErrorViewResolver

  1. @Component
  2. public class MyErrorViewResolver implements ErrorViewResolver {
  3. @Override
  4. public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
  5. ModelAndView mv = new ModelAndView();
  6. mv.setViewName("errorPage");
  7. mv.addObject("custommsg","请求出错辣");
  8. mv.addAllObjects(model);
  9. return mv;
  10. }
  11. }

image.png
在templates下自定义一个errorPage.html
image.pngimage.png
image.png

原理

image.pngimage.png

3. 完全自定义:替换ErrorController

image.pngimage.png

  1. @Controller
  2. public class MyErrorController extends BasicErrorController {
  3. @Autowired
  4. public MyErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
  5. super(errorAttributes, errorProperties);
  6. }
  7. @Override
  8. public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
  9. HttpStatus status = getStatus(request);
  10. // 因为要返回的是html,所以才把 媒体类型只定成了HTML
  11. Map<String,Object>model = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML));
  12. model.put("custommsg","出错辣!");
  13. ModelAndView mv = new ModelAndView();
  14. mv.setViewName("myErrorPage"); // 设置错误页
  15. mv.addAllObjects(model); // 设置错误数据
  16. mv.setStatus(status);
  17. return mv;
  18. }
  19. @Override
  20. public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  21. // 因为要返回任意类型,比如xml和json,所以把媒体类型定成 All
  22. Map<String,Object> body = getErrorAttributes(request,isIncludeStackTrace(request,MediaType.ALL));
  23. body.put("custommsg","出错辣!");
  24. HttpStatus status = getStatus(request);
  25. return new ResponseEntity<>(body,status);
  26. }
  27. }

image.pngimage.pngimage.pngimage.png

4. 总结

1.自定义数据的话,那么失败的页面所在位置不用自己去处理,而是使用框架默认的,比如/template/error。 只要在这里创建一个错误页面就好了。而且页面命名必须是 4xx,5xx或者精确响应码等。当然不同的响应码要找不同的错误页,如果不创建这些错误页或者找不到对应的,那么会默认使用框架自配的白页。

2.自定义视图的话,必须得手动配置失败页面所在的位置。和失败页面的名称。上面是直接配置了失败页面的名称为errorPage,那么此时就只能去/templates下的找这个页面了。而不是/templates/error/4xx、5xx了。
而且不管错误是4xx,还是5xx,还是2xx,错误响应页都只是这一个。
当然这里也可以对 错误数据进行处理(因为错误处理流程:出现错误 -> 封装错误数据 -> 封装错误视图 -> 返回)
可见,封装错误视图在封装错误数据之后,因此可以拿到错误数据,再进行改动。

3.对个别错误的处理。使用@ControllerAdvice + @ExceptionHandler(ArithmeticException.class) 处理特定异常。
这个是对单独异常特别处理。比如状态码500可以是空指针异常或者运算异常引起的。那么上面这个就可以处理这些空指针异常和运算异常。但是如果监听不到,还是会按照上面1和2流程走。如果监听到了,就按照自己的定义的来走。 (其实这个有点像 自定义错误页面)
image.png

异常处理流程

默认流程

image.png
1.执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束。用dispatchException承接异常。 进入视图解析流程,在这里进行异常处理。
image.pngimage.pngimage.pngimage.png
2.遍历所有的 目标方法异常解析器handlerExceptionResolver,
第一个找到 默认错误属性DefaultErrorAttributes,处理完之后继续找后面三个 目标方法异常解析器 处理,发现都不能处理,所以最后这个异常会被返回。

默认错误属性 处理过程:如上,仅仅是给request设置一个attribute,且key和value都是自定的。

image.pngimage.png
3.所有的 目标方法异常解析器 都不能处理之后,这个异常最后还是会被抛出。最后被底层所捕获,底层会重新发送一个/error请求(感觉很可能是response.sendError()在起作用)。然后就像一个正常的请求那样被处理。只不过/error请求的handler是 错误控制类BasicErrorController类型的。
然后然后就是正常的执行目标方法,像刚才说的,目标方法是错误控制类BasicErrorController类的errorHtml()方法。这个方法执行完后,返回的是modelAndView。

这个modelAndView里的model是通过拿到request里的属性封装的。而view是通过 调用父类 抽象错误控制类AbstractErrorController的resovleErrorView封装的。

抽象错误控制类 又调用了 Error视图解析类DefaultErrorViewResolver的resolveErrorView(),传入状态码封装成 error/+状态码,然后用这个字符串封装成一个view,再永model和view封装成ModelAndView,返回回去。

image.png

  1. 执行目标方法,返回modelAndView,即返回值不再是一个代表view的字符串名,而直接是一个modelAndView。那么ServletInvocableHandlerMethod处理返回值的时候,就不是调用ModelMethodProcessor或ViewNameMethodReturnValueHandler这种解析器了,而是直接第一个ModelAndVIewMethodReturnValueHandler,接下来就是视图解析的过程了。最后会找到模板下的error的404或500页面去返回。

5.如果用户没有这些页面的话,视图解析的时候就会返回系统写好的白页。通过response.getWrite().append()写出。

有 目标方法异常解析(handlerExceptionResolver)的流程

当所有的异常解析器都不能解析的时候,底层就会发送/error请求。
如果有了之后,就会交给这些异常处理去处理,这些异常处理会找到标注这个注解的方法,把他当成目标方法重新执行。(更简单点说,就是出现异常之后,就把标注这个注解的方法重新当成目标方法,继续走流程。就是原来的比如/basic_table变成了下面的/login。 如果交给底层处理的话,就是变成了/error/4xx或5xx)

ExceptionHandlerExceptionResolver

image.pngimage.png
得有:
1.@ControllerAdvice
2.@ExceptionHandler(1,2)

运行过程:
如果执行目标方法的时候,遇到了ArithmeticException或NullPointerException异常,就会进入这个异常类里,最后界面返回login页面
image.png

ResponseStatusExceptionResolver

image.png
这类,就是拿到ResponseStatus注解里的value(状态码)、reason(错误信息),然后把他们封装成model,最后再用response.sendError(),相当于让tomcat发送/error请求。
image.pngimage.png

DefaultHandlerExceptionResolver

专门用来处理springboot底层的一些异常的。比如,下面这个。他的处理方式和ResponseStatusExceptionResolver差不多,都是给出状态码和消息,用来封装成model的。底层用的view还是/error
image.pngimage.png
image.png

自定义目标方法异常解析类

image.png
一定要设置优先级,否则他就会在最下面,将没有什么可接管的异常(因为无论是springboot底层异常或者其他,上面两个基本都能接管,只有404不能接管。而如果优先级放到最高,那么基本我们什么异常都能接管。没后面什么事了)
(能接管哪种类型的异常,是可以设置的。)
image.png

  • ErrorViewResolver 实现自定义处理异常;
    • response.sendError 。error请求就会转给controller
    • 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
    • basicErrorController 要去的页面地址是 ErrorViewResolver


异常处理相关类

错误控制类

继承关系

image.png

ErrorController(Error控制类):接口(这个接口非常不重要)

属性无

行为:只有一个getErrorPath(),而且这个行为已经被废弃

image.png

AbstractErrorController(抽象Error控制类):类

属性:两个,errorViewResolvers(错误视图解析器)、errorAttributes(错误属性,不重要)

image.png

行为:主要看resolverErrorView(),返回ModelAndView。作用是遍历所有的视图解析器,找到合适的Error视图解析器去解析。虽说是遍历,其实这个视图解析器目前也仅有DefaultErrorViewResolver这一个解析器而已。

image.pngimage.png

BasicErrorController:类

image.png
如上,这个类主要用于处理/error请求 ${}代表是动态取值 冒号代表,如果前面没有就用后面

属性:errorProperties,只有一个,而且不重要

image.png

行为:error()返回实体、errorHtml()返回ModelAndView。只有这两个重要。这两个分别是相应机器端的/error请求和浏览器端的/error

image.pngimage.png
image.png

ErrorAttributes(Error属性):这个接口不重要(在AbstractErrorController里做一个属性)

image.png

DefaultErrorAttributes类,重要!实现了ErrorAttributes和HandlerExceptionResolver

此类作用是:拿出所有的异常属性,message、trace、status、path等
image.pngimage.pngimage.png

ErrorProperties(Error属性):这个类也不重要(在BasicErrorController里做一个属性)

image.pngimage.png

上面那么多的介绍,主要是为了这里

BasicErrorController的error()返回实体、errorHtml()返回ModelAndView。只有这两个重要。这两个分别是相应机器端的/error请求和浏览器端的/error
这个类主要用于处理/error请求 ${}代表是动态取值 冒号代表,如果前面没有就用后面
AbstractErrorController的resolverErrorView(),返回ModelAndView。作用是遍历所有的视图解析,找到合适的Error视图解析器去解析。

虽说是遍历,其实这个视图解析器目前也仅有DefaultErrorViewResolver这一个解析器而已。其实就相当于传入 request,response,status(状态码),model这四个参数给DefaultErrorViewResolver#resolveErrorView()去解析而已。解析就是把状态码拼接成view字符串,并且根据字符串创建view,然后用view和model创建ViewAndModel

Error视图解析器

继承关系

image.png

ErrorViewResolver(Error视图解析器):接口,只有一个行为,无属性

行为:resolverErrorView(),作用是解析请求,并且返回解析后的ModelAndView

image.png

DefaultErrorViewResolver(默认Error视图解析器):上面的唯一实现类

作用:用error/+状态码封装 view,然后view结合传来的model,一起封装到ModelAndView里,通过resolveErrorView()返回。

属性:都不是很重要,重点关注其静态代码块:创建一个view,放入客户端错误4xx,服务端错误5xx

image.png

行为:resolverErrorView()、resolve()、resolveResource()

image.pngimage.pngimage.png

上边两个介绍主要是为了这里:

主要功能:上面三个方法连起来解析:resovleErrorView()拿到状态码(400或500)交给resolve(),resovler把状态码和字符串”error/“ 拼接,变成视图名(比如error/404)交给resovleResource()(一般不会执行到这一步,而是直接返回error/404,后面会给他自动加上后缀的。)。resolveResource()拿到之后和”.html”拼接,得到完成的视图名(比如error/404.html),然后用这个视图名字符串创建一个view,和model一起当参数创建一个ModelAndView并返回。完成视图解析.

异常自动配置

ErrorMvcAutoConfiguration

这个的作用:这个类里很多方法都是为了往容器里添加组件。
image.pngimage.pngimage.png

内部类1:WhitelabelErrorViewConfiguration(白页类)

这个类里还有几个内部类,里面也是为了往容器里增加相应的组件,比如下面的白页类。image.png
image.pngimage.pngimage.pngimage.pngimage.png
此类目的是创建一个白页,这样当找不到用户自定义的404或500的时候,可以用这个页面顶上去。
这个页面就是一个静态html。
StaticView本质上也就是一个View
返回空白页的逻辑:
创建一个StringBuilder,往里面写很多内容,包括 model里的时间戳、消息、栈轨迹,以及html内容。
最后用response.getWrite()写入。(把builder里的内容toString())

上面的介绍主要是为了这里:

上面的类作用就是为了往容器里增加组件,共加了如下组件:
默认错误属性DefaultErrorAttributes、错误控制类BasicErrorController、
默认错误解析器DefaultErrorViewResolver(组件名是conventionErrorViewResolver)、默认错误View页面defaultErrorView、
beanName视图解析器BeanNameViewResolver(作用是根据beanName找到相应的View)

视图解析接口

ViewResolver视图解析器的继承关系

image.png

ViewResolver(视图解析器): 接口

视图解析器和错误视图解析(即ErrorViewResolver)没有任何关系。

行为:解析视图名字resolveViewName(),只有这一个

image.png

BeanNameViewResolver(视图解析器一个实现类):类

image.png
如上可以,通过beanName从容器里拿相应的View。

目标方法异常解析器

HandlerExceptionResolver目标方法异常处理的继承关系

image.png

HandlerExceptionResolver:接口

image.png

行为:resolveException,只有这一个。 解析异常。

他的主要实现类有三个。
错误页、异常处理 - 图81

ExceptionHandlerExceptionResolver异常:看名字,ExceptionHandler,就是目标方法异常之后,判断目标方法是否标注ExceptionHandler,如果标注,就归他处理。
ExceptionHandler:翻译,异常+目标方法(handler = handlerMethod),即目标方法是否异常,如果异常,判断是否加注解