- 1、错误页
- 异常处理流程
- 异常处理相关类
- 错误控制类
- ErrorController(Error控制类):接口(这个接口非常不重要)
- AbstractErrorController(抽象Error控制类):类
- BasicErrorController:类
- ErrorAttributes(Error属性):这个接口不重要(在AbstractErrorController里做一个属性)
- DefaultErrorAttributes类,重要!实现了ErrorAttributes和HandlerExceptionResolver
- ErrorProperties(Error属性):这个类也不重要(在BasicErrorController里做一个属性)
- 上面那么多的介绍,主要是为了这里
- Error视图解析器
- 异常自动配置
- 视图解析接口
- 目标方法异常解析器
1、错误页
1. 雷神
1.1 官方错误处理逻辑
1.1.1 默认规则
翻译过来:
- 默认情况下,Spring Boot提供
/error
处理所有错误的映射 - 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
- 要对其进行自定义,添加
**View**
解析为 - 要完全替换默认行为,可以实现
ErrorController
并注册该类型的Bean定义,或添加ErrorAttributes类型的组件
以使用现有机制但替换其内容。 - error/下的4xx,5xx页面会被自动解析;
1.1.2 原理:BasicErrorController类。
1.1.3 注意:响应码.html优先级高于4xx.html
1.2 定制错误处理逻辑
如果在 /resources/public/error、/resources/templates/error里自定义404和500等会被默认配置。
5xx表示所有以5开头的状态信息,都能和这个匹配
- 自定义错误页
- 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());
- 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则
- 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类,那么框架就会用我们自己提供的错误数据类。
自定义数据错误类:添加cutommsg属性,移除error属性
@Component
public class MyErrorAttribute extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String,Object>errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace);
errorAttributes.put("custommsg","请求出错啦");
errorAttributes.remove("error");
System.err.println(errorAttributes);
return errorAttributes;
}
}
写前端页面
- 进行错误访问,查看运行结果
原理:
所有的一切由ErrorMvcAutoConfiguration总配置。
BasicErrorController的errorHtml()和error()通过getErrorAttribute()获取Error信息。
最终会调用DefaultErrorAttributes 类的 getErrorAttributes()。因为只需重写 getErrorAttributes()
当系统没有提供ErrorAttributes的时候,才会采用DefaultErrorAttributes。
因此只需自己提供一个 ErrorAttributes 即可,而 DefaultErrorAttributes 是 ErrorAttributes 的子类,因此只需要继承 DefaultErrorAttributes 即可。
2. 自定义错误视图:替换ErrorViewResolver
实现 ErrorViewResolver
@Component
public class MyErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView mv = new ModelAndView();
mv.setViewName("errorPage");
mv.addObject("custommsg","请求出错辣");
mv.addAllObjects(model);
return mv;
}
}
在templates下自定义一个errorPage.html
原理
3. 完全自定义:替换ErrorController
@Controller
public class MyErrorController extends BasicErrorController {
@Autowired
public MyErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
super(errorAttributes, errorProperties);
}
@Override
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
// 因为要返回的是html,所以才把 媒体类型只定成了HTML
Map<String,Object>model = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML));
model.put("custommsg","出错辣!");
ModelAndView mv = new ModelAndView();
mv.setViewName("myErrorPage"); // 设置错误页
mv.addAllObjects(model); // 设置错误数据
mv.setStatus(status);
return mv;
}
@Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
// 因为要返回任意类型,比如xml和json,所以把媒体类型定成 All
Map<String,Object> body = getErrorAttributes(request,isIncludeStackTrace(request,MediaType.ALL));
body.put("custommsg","出错辣!");
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body,status);
}
}
4. 总结
1.自定义数据的话,那么失败的页面所在位置不用自己去处理,而是使用框架默认的,比如/template/error。 只要在这里创建一个错误页面就好了。而且页面命名必须是 4xx,5xx或者精确响应码等。当然不同的响应码要找不同的错误页,如果不创建这些错误页或者找不到对应的,那么会默认使用框架自配的白页。
2.自定义视图的话,必须得手动配置失败页面所在的位置。和失败页面的名称。上面是直接配置了失败页面的名称为errorPage,那么此时就只能去/templates下的找这个页面了。而不是/templates/error/4xx、5xx了。
而且不管错误是4xx,还是5xx,还是2xx,错误响应页都只是这一个。
当然这里也可以对 错误数据进行处理(因为错误处理流程:出现错误 -> 封装错误数据 -> 封装错误视图 -> 返回)
可见,封装错误视图在封装错误数据之后,因此可以拿到错误数据,再进行改动。
3.对个别错误的处理。使用@ControllerAdvice + @ExceptionHandler(ArithmeticException.class) 处理特定异常。
这个是对单独异常特别处理。比如状态码500可以是空指针异常或者运算异常引起的。那么上面这个就可以处理这些空指针异常和运算异常。但是如果监听不到,还是会按照上面1和2流程走。如果监听到了,就按照自己的定义的来走。 (其实这个有点像 自定义错误页面)
异常处理流程
默认流程
1.执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束。用dispatchException承接异常。 进入视图解析流程,在这里进行异常处理。
2.遍历所有的 目标方法异常解析器handlerExceptionResolver,
第一个找到 默认错误属性DefaultErrorAttributes,处理完之后继续找后面三个 目标方法异常解析器 处理,发现都不能处理,所以最后这个异常会被返回。
默认错误属性 处理过程:如上,仅仅是给request设置一个attribute,且key和value都是自定的。
3.所有的 目标方法异常解析器 都不能处理之后,这个异常最后还是会被抛出。最后被底层所捕获,底层会重新发送一个/error请求(感觉很可能是response.sendError()在起作用)。然后就像一个正常的请求那样被处理。只不过/error请求的handler是 错误控制类BasicErrorController类型的。
然后然后就是正常的执行目标方法,像刚才说的,目标方法是错误控制类BasicErrorController类的errorHtml()方法。这个方法执行完后,返回的是modelAndView。
这个modelAndView里的model是通过拿到request里的属性封装的。而view是通过 调用父类 抽象错误控制类AbstractErrorController的resovleErrorView封装的。
抽象错误控制类 又调用了 Error视图解析类DefaultErrorViewResolver的resolveErrorView(),传入状态码封装成 error/+状态码,然后用这个字符串封装成一个view,再永model和view封装成ModelAndView,返回回去。
- 执行目标方法,返回modelAndView,即返回值不再是一个代表view的字符串名,而直接是一个modelAndView。那么ServletInvocableHandlerMethod处理返回值的时候,就不是调用ModelMethodProcessor或ViewNameMethodReturnValueHandler这种解析器了,而是直接第一个ModelAndVIewMethodReturnValueHandler,接下来就是视图解析的过程了。最后会找到模板下的error的404或500页面去返回。
5.如果用户没有这些页面的话,视图解析的时候就会返回系统写好的白页。通过response.getWrite().append()写出。
有 目标方法异常解析(handlerExceptionResolver)的流程
当所有的异常解析器都不能解析的时候,底层就会发送/error请求。
如果有了之后,就会交给这些异常处理去处理,这些异常处理会找到标注这个注解的方法,把他当成目标方法重新执行。(更简单点说,就是出现异常之后,就把标注这个注解的方法重新当成目标方法,继续走流程。就是原来的比如/basic_table变成了下面的/login。 如果交给底层处理的话,就是变成了/error/4xx或5xx)
ExceptionHandlerExceptionResolver
得有:
1.@ControllerAdvice
2.@ExceptionHandler(1,2)
运行过程:
如果执行目标方法的时候,遇到了ArithmeticException或NullPointerException异常,就会进入这个异常类里,最后界面返回login页面
ResponseStatusExceptionResolver
这类,就是拿到ResponseStatus注解里的value(状态码)、reason(错误信息),然后把他们封装成model,最后再用response.sendError(),相当于让tomcat发送/error请求。
DefaultHandlerExceptionResolver
专门用来处理springboot底层的一些异常的。比如,下面这个。他的处理方式和ResponseStatusExceptionResolver差不多,都是给出状态码和消息,用来封装成model的。底层用的view还是/error
自定义目标方法异常解析类
一定要设置优先级,否则他就会在最下面,将没有什么可接管的异常(因为无论是springboot底层异常或者其他,上面两个基本都能接管,只有404不能接管。而如果优先级放到最高,那么基本我们什么异常都能接管。没后面什么事了)
(能接管哪种类型的异常,是可以设置的。)
- ErrorViewResolver 实现自定义处理异常;
- response.sendError 。error请求就会转给controller
- 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
- basicErrorController 要去的页面地址是 ErrorViewResolver ;
异常处理相关类
错误控制类
继承关系
ErrorController(Error控制类):接口(这个接口非常不重要)
行为:只有一个getErrorPath(),而且这个行为已经被废弃
AbstractErrorController(抽象Error控制类):类
属性:两个,errorViewResolvers(错误视图解析器)、errorAttributes(错误属性,不重要)
行为:主要看resolverErrorView(),返回ModelAndView。作用是遍历所有的视图解析器,找到合适的Error视图解析器去解析。虽说是遍历,其实这个视图解析器目前也仅有DefaultErrorViewResolver这一个解析器而已。
BasicErrorController:类
如上,这个类主要用于处理/error请求 ${}代表是动态取值 冒号代表,如果前面没有就用后面
属性:errorProperties,只有一个,而且不重要
行为:error()返回实体、errorHtml()返回ModelAndView。只有这两个重要。这两个分别是相应机器端的/error请求和浏览器端的/error
ErrorAttributes(Error属性):这个接口不重要(在AbstractErrorController里做一个属性)
DefaultErrorAttributes类,重要!实现了ErrorAttributes和HandlerExceptionResolver
此类作用是:拿出所有的异常属性,message、trace、status、path等
ErrorProperties(Error属性):这个类也不重要(在BasicErrorController里做一个属性)
上面那么多的介绍,主要是为了这里
BasicErrorController的error()返回实体、errorHtml()返回ModelAndView。只有这两个重要。这两个分别是相应机器端的/error请求和浏览器端的/error
这个类主要用于处理/error请求 ${}代表是动态取值 冒号代表,如果前面没有就用后面
AbstractErrorController的resolverErrorView(),返回ModelAndView。作用是遍历所有的视图解析,找到合适的Error视图解析器去解析。
虽说是遍历,其实这个视图解析器目前也仅有DefaultErrorViewResolver这一个解析器而已。其实就相当于传入 request,response,status(状态码),model这四个参数给DefaultErrorViewResolver#resolveErrorView()去解析而已。解析就是把状态码拼接成view字符串,并且根据字符串创建view,然后用view和model创建ViewAndModel
Error视图解析器
继承关系
ErrorViewResolver(Error视图解析器):接口,只有一个行为,无属性
行为:resolverErrorView(),作用是解析请求,并且返回解析后的ModelAndView
DefaultErrorViewResolver(默认Error视图解析器):上面的唯一实现类
作用:用error/+状态码封装 view,然后view结合传来的model,一起封装到ModelAndView里,通过resolveErrorView()返回。
属性:都不是很重要,重点关注其静态代码块:创建一个view,放入客户端错误4xx,服务端错误5xx
行为:resolverErrorView()、resolve()、resolveResource()
上边两个介绍主要是为了这里:
主要功能:上面三个方法连起来解析:resovleErrorView()拿到状态码(400或500)交给resolve(),resovler把状态码和字符串”error/“ 拼接,变成视图名(比如error/404)交给resovleResource()(一般不会执行到这一步,而是直接返回error/404,后面会给他自动加上后缀的。)。resolveResource()拿到之后和”.html”拼接,得到完成的视图名(比如error/404.html),然后用这个视图名字符串创建一个view,和model一起当参数创建一个ModelAndView并返回。完成视图解析.
异常自动配置
ErrorMvcAutoConfiguration
这个的作用:这个类里很多方法都是为了往容器里添加组件。
内部类1:WhitelabelErrorViewConfiguration(白页类)
这个类里还有几个内部类,里面也是为了往容器里增加相应的组件,比如下面的白页类。
此类目的是创建一个白页,这样当找不到用户自定义的404或500的时候,可以用这个页面顶上去。
这个页面就是一个静态html。
StaticView本质上也就是一个View
返回空白页的逻辑:
创建一个StringBuilder,往里面写很多内容,包括 model里的时间戳、消息、栈轨迹,以及html内容。
最后用response.getWrite()写入。(把builder里的内容toString())
上面的介绍主要是为了这里:
上面的类作用就是为了往容器里增加组件,共加了如下组件:
默认错误属性DefaultErrorAttributes、错误控制类BasicErrorController、
默认错误解析器DefaultErrorViewResolver(组件名是conventionErrorViewResolver)、默认错误View页面defaultErrorView、
beanName视图解析器BeanNameViewResolver(作用是根据beanName找到相应的View)
视图解析接口
ViewResolver视图解析器的继承关系
ViewResolver(视图解析器): 接口
视图解析器和错误视图解析(即ErrorViewResolver)没有任何关系。
行为:解析视图名字resolveViewName(),只有这一个
BeanNameViewResolver(视图解析器一个实现类):类
如上可以,通过beanName从容器里拿相应的View。
目标方法异常解析器
HandlerExceptionResolver目标方法异常处理的继承关系
HandlerExceptionResolver:接口
行为:resolveException,只有这一个。 解析异常。
他的主要实现类有三个。
ExceptionHandlerExceptionResolver异常:看名字,ExceptionHandler,就是目标方法异常之后,判断目标方法是否标注ExceptionHandler,如果标注,就归他处理。
ExceptionHandler:翻译,异常+目标方法(handler = handlerMethod),即目标方法是否异常,如果异常,判断是否加注解