/**
* <pre>
*
* 前置概念:
*
* Servlet规范中定义的一种机制ErrorPage,用于处理发生在Servlet容器中的错误,当Servlet容器无法处理请求时
* (比如404页面不存在、500服务器错误等), 会根据配置的ErrorPage来展示相应的错误页面
*
* 在Tomcat处理请求的时候,出现异常就会找给Tomcat上下中注册的ErrorPage对象,其中ErrorPage中存在code,异常类型和转发的路径
* 并且,会先根据响应错误码来找设置了一致code的ErrorPage,否则按照异常类型来找ErrorPage对象
* 找了ErrorPage对象之后,会在request作用域中存入一大堆的数据,最终又会调用Servlet的API对ErrorPage中的location的URL进行转发
* RequestDispatcher rd = servletContext.getRequestDispatcher(errorPage.getLocation()).include(request.getRequest(), response.getResponse());;
*
* 在SpringBoot中,{@link org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration}配置了TomcatServletWebServerFactory
* 该类就是用于创建Tomcat对象的,对Tomcat的配置都在该工厂中配置,所以,对于ErrorPage,就是在{@link org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#configureContext}完成的
* SpringBoot会将自身配置的ErrorPage对象,一一转换为Tomcat提供的ErrorPage对象,然后注册到Tomcat上下文中
*
* 而如何在SpringBoot中注册ErrorPage呢? 在SpringBoot的Web服务器的自动配置中,注入的一个{org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor}的Bean
* 这个BeanPostProcessor在Bean的初始化前,会从Spring容器中获取ErrorPageRegistrar类型的Bean,然后通过回调ErrorPageRegistrar.registerErrorPages来注册ErrorPage
* 而SpringBoot又提供了一个{@link org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.ErrorPageCustomizer},注册了一个默认的ErrorPage
* // 接收ServerProperties的error属性中的path来配置默认的该ErrorPage转发的路径,默认为/error @Value("${error.path:/error}")
* ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
* errorPageRegistry.addErrorPages(errorPage);
*
* 在SpringMVC中,就只能通过web.xml中配置ErrorPage,因为web.xml是tomcat需要的配置文件,对于tomcat的配置都可以在web.xml中配置
*
* 如果不需要用到ErrorPage机制,那么我们可以利用SpringMVC提供的异常解析器机制,使用自定义的HandlerExceptionResolver或者SpringMVC提供的异常解析器
* 返回ModelAndView,设置自己固定的错误页面或者动态设置页面都可以, 这样,在出现异常的情况下,SpringMVC就可以处理,不需要将异常抛到Tomcat
*
*
* 在Springboot-springmvc异常处理的核心原理{@link DispatcherServlet#processHandlerException}
*
*
* 最后,在SpringBoot默认提供的ErrorPage有专门的处理类{@link org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration}
* 该类的作用:
* 1. BasicErrorController,处理/error请求的类
* 2. DefaultErrorAttributes该类是一个HandlerExceptionResolver,进行异常解析
* 3. ErrorPageCustomizer: 注册ErrorPage,规定路径为/error(默认,可以修改)
* 4. PreserveErrorControllerTargetClassPostProcessor: 该类会对所有实现了ErrorController接口的BeanDefinition中设置preserveTargetClass,使用CBLIB代理
* 5. DefaultErrorViewResolver: 默认的错误视图解析器,可以解析error/目录下的4xx,5xx.html视图
* 6: StaticView: /error返回的View视图,因为/error可以返回页面,json,多种情况都要兼容
* 7: BeanNameViewResolver: 解析View(StaticView)对象的视图解析器
* </pre>
*/
public class DispatcherServlet {
// 请求处理的核心逻辑
public void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 正常执行返回的结果
ModelAndView mv = null;
// 执行过程中是否出现异常
Exception dispatchException = null;
try {
// 获取处理请求的Handler类
mappedHandler = getHandler(processedRequest);
// 执行Handler的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
} catch (Exception ex) {
// 执行过程出现异常
dispatchException = ex;
} catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 处理最终的结果
this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
/**
* 处理最终结果
*
* @param request 请求对象
* @param response 响应对象
* @param mappedHandler 处理请求的handler类
* @param mv 正确执行返回的模型和视图对象,如果出现异常则没有
* @param exception 执行过程中的异常对象
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, Exception exception) {
// 是否存在异常的view
boolean errorView = false;
// 如果存在异常对象,表示出现异常了
if (exception != null) {
// 获取到处理接口的Handler
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// 处理异常情况,使用异常解析器,看是否能处理该异常情况,并且返回一个模型和视图对象
mv = this.processHandlerException(request, response, handler, exception);
// 异常解析器如果成功返回ModelAndView,表示给定了指定的视图
errorView = (mv != null);
}
// 存在ModelAndView对象
if (mv != null && !mv.wasCleared()) {
// 就可以调用View对象的render方法
this.render(mv, request, response);
// 如果存在异常的ModelAndView对象
if (errorView) {
// 删除保存到请求域中的异常数据,处理异常的时候保存的
WebUtils.clearErrorRequestAttributes(request);
}
}
}
// 视图解析渲染
public void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 最终渲染的视图对象
View view;
// 获取视图名称
String viewName = mv.getViewName();
// 如果存在视图名,就需要根据视图解析器根据名称解析成View对象
if (viewName != null) {
// 根据所有的视图解析器对该名称进行解析,直到解析到View对象为止
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 如果所有视图解析器都无法解析该视图名,抛出异常,因为没有视图对象View可以渲染
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'");
}
} else {
// 没有视图名,那么就可能直接在ModelAndView中设置的是View对象
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'");
}
}
// 如果设置了状态
if (mv.getStatus() != null) {
// 保存该状态,并且设置到响应对象中
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
response.setStatus(mv.getStatus().value());
}
// 调用View对象的渲染流程,该转发转发,重定向重定向,自定义就自定义
view.render(mv.getModelInternal(), request, response);
}
// 处理异常的核心逻辑
public ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 最终异常解析器处理完成返回的模型视图对象
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
// 遍历所有的异常解析器对象
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
// 执行解析异常逻辑,返回ModelAndView对象
exMv = resolver.resolveException(request, response, handler, ex);
// 直到有异常解析器返回ModelAndView结束
if (exMv != null) {
break;
}
}
}
// 如果异常解析器解析完成,并且解析到了ModelAndView
if (exMv != null) {
// 返回的是否是一个空视图,并且Model中也没有数据,直接结束,也不需要干嘛了
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// 如果不存在视图View对象,要执行一些默认的操作,比如按照默认的策略找视图名
if (!exMv.hasView()) {
// 获取默认的视图名,默认情况url就是视图名
String defaultViewName = getDefaultViewName(request);
// 如果url获取到视图名
if (defaultViewName != null) {
// 保存需要解析该视图对象
exMv.setViewName(defaultViewName);
}
}
// 将异常信息保存到请求域中
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
// 只要拿到ModelAndView,那么SpringMVC就会当正常逻辑来进行视图解析渲染
return exMv;
}
// 如果没有视图解析能解析该异常并返回ModelAndVie对象,则继续抛出异常
throw ex;
}
}