请选择 进入手机版 | 继续访问电脑版

常请求处理流程 发送/error请求以及得到error视图的详细原理

[复制链接]
丁翼 发表于 2021-1-1 18:33:32 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
Spring MVC SpringBoot源码剖析:异常请求处理流程;发送/error并返回error视图的详细原理

引言

一个错误请求的处理流程主要是这样的:

  • 当发送一个错误请求时,spring mvc会实验处理这个请求,好比实验去寻找静态资源等等;
  • 如果处理失败,会将错误信息生存,然后重新发送一个/error请求;
  • /error请求会被errorController处理,返回一个error视图;
本文主要通过源码分析异常请求处理的整个流程,对于springboot提供的BasicErrorController等剖析error的组件不做过多形貌;
SpringBoot默认处理规则

默认情况下,Spring Boot提供 “/error” 处理所有错误的映射(BasicErrorController);
对于呆板客户端,它将生成JSON响应,此中包罗错误,HTTP状态和异常消息的详细信息;

对于欣赏器客户端,响应一个“ whitelabel”错误视图,以HTML格式出现相同的数据;

不论error html页面照旧error json,能够得到的属性是一样的,有:
timestamp, status, error, exception, message, errors, trace, path
*异常请求处理流程

异常请求处理的流程主要分成四步:

  • 剖析错误请求被作为异常抛出
  • 实验处理错误请求
  • 如果处理失败,会给底层response发送错误信息;然后再重新发送一个/error请求
  • /error请求被ErrorController处理(SpringBoot帮我们自动设置了BasicErrorController)
步调一:将错误请求作为异常抛出

当发送请求时,DispatcherServlet的doDispatch方法会寻找合适的Handler,然后通过Handler去找合适的Adapter去最终处理这个请求;
当发送错误请求时,寻找Handler过程就会抛出异常并被catch捕获;
  1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {    ...                catch (Exception ex) {                           dispatchException = ex;                }        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);    ...
复制代码
捕获的异常会调用processDispatchResult实验处理异常;
例:当发送一个405请求:Method Not Allowed,
部分栈轨迹:
  1. handleNoMatch:250, RequestMappingInfoHandlerMapping (org.springframework.web.servlet.mvc.method)lookupHandlerMethod:417, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)getHandlerInternal:364, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)getHandlerInternal:123, RequestMappingInfoHandlerMapping (org.springframework.web.servlet.mvc.method)getHandlerInternal:66, RequestMappingInfoHandlerMapping (org.springframework.web.servlet.mvc.method)getHandler:491, AbstractHandlerMapping (org.springframework.web.servlet.handler)getHandler:1255, DispatcherServlet (org.springframework.web.servlet)doDispatch:1037, DispatcherServlet (org.springframework.web.servlet)doService:961, DispatcherServlet (org.springframework.web.servlet)processRequest:1006, FrameworkServlet (org.springframework.web.servlet)doGet:898, FrameworkServlet (org.springframework.web.servlet)service:626, HttpServlet (javax.servlet.http)service:883, FrameworkServlet (org.springframework.web.servlet)service:733, HttpServlet (javax.servlet.http)
复制代码
最终handleNoMatch方法抛出HttpRequestMethodNotSupportedException:
  1. @Overrideprotected HandlerMethod handleNoMatch(      Set infos, String lookupPath, HttpServletRequest request) throws ServletException {   PartialMatchHelper helper = new PartialMatchHelper(infos, request);   if (helper.isEmpty()) {      return null;   }   if (helper.hasMethodsMismatch()) {      Set methods = helper.getAllowedMethods();      if (HttpMethod.OPTIONS.matches(request.getMethod())) {         HttpOptionsHandler handler = new HttpOptionsHandler(methods);         return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);      }      throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);   }
复制代码
步调二:实验处理异常

抛出的异常都会被DispatcherServlet.doDispatch的catch捕获到,然后带着这个异常信息调用processDispatchResult方法:
这个方法首先会实验处理异常信息去获取ModelAndView对象:
该方法的主要逻辑:

  • 寻找能够处理异常的对象:如果是ModelAndView界说异常,会返回一个特殊的视图(表明视图界说异常);否则,说明是handler的异常,会调用processHandlerException处理(详细看下面该方法的处理流程);
  • 如果最终乐成处理了异常,会得到ModelAndView对象,然后将视图渲染,处理完成;否则抛出异常。
  1. private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,      @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,      @Nullable Exception exception) throws Exception {   boolean errorView = false;   if (exception != null) {      if (exception instanceof ModelAndViewDefiningException) {         logger.debug("ModelAndViewDefiningException encountered", exception);         mv = ((ModelAndViewDefiningException) exception).getModelAndView();      }      else {         Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);         mv = processHandlerException(request, response, handler, exception);         errorView = (mv != null);      }   }   // 将得到的视图渲染   if (mv != null && !mv.wasCleared()) {      render(mv, request, response);       //渲染完成,将异常处理器DefaultErrorAttributes中参加request域的错误信息清除      if (errorView) {         WebUtils.clearErrorRequestAttributes(request);      }   }}
复制代码
processHandlerException会遍历所有handler异常的剖析器去实验处理这个异常
DefaultErrorAttributes和HandlerExceptionResolverComposite);

最终返回的是ModelAndView对象,如果返回的非空,说明异常处理乐成了;
  1. @Nullableprotected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,      @Nullable Object handler, Exception ex) throws Exception {   // 寻找注册的HandlerExceptionResolvers去剖析handlerException   ModelAndView exMv = null;   if (this.handlerExceptionResolvers != null) {      for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {          //HandlerExceptionResolver实验剖析异常,如果能返回MV对象说明乐成剖析         exMv = resolver.resolveException(request, response, handler, ex);         if (exMv != null) {            break;         }      }   }   if (exMv != null) {       //如果得到了ModelAndView对象,但该对象的view和model都是空      if (exMv.isEmpty()) {         request.setAttribute(EXCEPTION_ATTRIBUTE, ex);         return null;      }            if (!exMv.hasView()) {         String defaultViewName = getDefaultViewName(request);         if (defaultViewName != null) {            exMv.setViewName(defaultViewName);         }      }            WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());      return exMv;   }   throw ex;}
复制代码
剖析handlerException的过程:

  • 首先会由DefaultErrorAttributes处理异常,它并不会真正处理异常,只是把异常信息放到request域中;
  1. @Overridepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {   storeErrorAttributes(request, ex);   return null;}private void storeErrorAttributes(HttpServletRequest request, Exception ex) {   request.setAttribute(ERROR_ATTRIBUTE, ex);}
复制代码

  • 然后由HandlerExceptionResolverComposite处理异常,该对象由三个HandlerExceptionResolver组成,见上图;如果所有的都不能处理这个异常,这个异常就会抛出;
步调三:对无法管理的请求异常给响应(response)写error信息,然后底层重新发一个/error请求


  • 竣事无法处理的异常请求:假设processHandlerException方法最终无法处理该请求异常,那么就会调用response.sendError给响应写错误信息,并最终挂起本次响应(setSuspended)从而竣事请求;
  1. public class Response implements HttpServletResponse {        @Override        public void sendError(int status, String message) throws IOException {                //如果是已经提交的请求大概是servlet内部的请求,就不需要发送错误信息            if (isCommitted()) {                throw new IllegalStateException(sm.getString("coyoteResponse.sendError.ise"));            }            if (included) {                return;            }                    setError();            getCoyoteResponse().setStatus(status);            getCoyoteResponse().setMessage(message);                    //清空数据缓存并挂起响应            // Clear any data content that has been buffered            resetBuffer();            // Cause the response to be finished (from the application perspective)            setSuspended(true);        }
复制代码

  • 重新发送一个/error请求:ApplicationFilterChain的invoke方法:

    • 先将该请求响应对的挂起取消:response.setSuspended(false);
    • 然后调用status方法重新生成一个/error请求;

status(request, response):将指定请求生成的http状态码和相关的信息举行处理,从而产生特定的响应;
status方法中将原请求改为/error并给request添加之前剖析得到的错误信息:
  1. request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, errorPage.getLocation());//将请求改为/errorrequest.setAttribute(Globals.DISPATCHER_TYPE_ATTR,DispatcherType.ERROR);...
复制代码
然后,/error请求又会颠末doDispatch去寻找能够映射这个请求的controller;默认的,这个controller就是springboot帮我们设置的BasicErrorController;
步调四:/error请求被BasicErrorController映射并处理

若没有重写ErrorController方法并注册到容器中,调用默认的(springboot帮忙设置的)BasicErrorController;该类只有两个处理请求(带@RequestMapping)的方法:


  • 如果欣赏器发送的请求,会被errorHTML方法处理(produces = MediaType.TEXT_HTML_VALUE体现返回值范例是text/html);
  • 如果是呆板客户端,就会被**error(HttpServletRequest request):ResponseEntity**方法处理;
errorHTML主要做了两件事:

  • 该方法会将request的错误信息封装到model中;然后去剖析错误视图;


  • 剖析错误视图会调用错误视图剖析器完成,默认的只有一个:DefaultErrorViewResolver;
  1.         @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)        //MediaType.TEXT_HTML_VALUE=text/html        public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {                        HttpStatus status = getStatus(request);                Map model = Collections.unmodifiableMap(                            getErrorAttributes(request,getErrorAttributeOptions(request, MediaType.TEXT_HTML)));                        response.setStatus(status.value());                ModelAndView modelAndView = resolveErrorView(request, response, status, model);                return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);        }        //剖析错误视图会通不对误视图剖析器来完成        protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map model) {        //this.errorViewResolvers = DefaultErrorViewResolver                for (ErrorViewResolver resolver : this.errorViewResolvers) {                        ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);                        if (modelAndView != null) {                                return modelAndView;                        }                }                return null;        }
复制代码
DefaultErrorViewResolver剖析错误视图主要的流程是:

  • resolveErrorView方法首先得到错误的http状态码,并根据状态码名称调用resolve方法:

    • resolve方法去/templates/error下面寻找名称与状态码对应的模板,如404.html,如果有就会将其渲染成视图,返回ModelAndView对象;
    • 如果没有,再调用resolveResource去静态资源目次下寻找:
      ​ “classpath:/META-INF/resources/”, “classpath:/resources/”, “classpath:/static/”, “classpath:/public/”
    • 若仍然没有,resolve方法最终返回ModelAndView为null;

  • 当ModelAndView为null,再去验证错误代码是否是4xx或5xx,然后再按照上面resolve方法的逻辑去找是否有4xx.html或5xx.html;
  • 如果仍然没找到,返回null;
综上所述,错误视图剖析器首先会去/templates/error(和静态资源目次)下找精确匹配的模板(404.html,405.html, …);如果没找到,再去找暗昧匹配的,如状态码是405就去找4xx.html;如果最后照旧没有,返回null。
  1. @Overridepublic ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map model) {   ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);    //当没有找到名称完全相符的,如(404.html),再去找是否有4xx.html和5xx.html的错误模板   if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {        //Series_VIEWS: "4xx","5xx"      modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);   }   return modelAndView;}private ModelAndView resolve(String viewName, Map model) {   String errorViewName = "error/" + viewName;   TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,         this.applicationContext);    //如果在/templates/error下找到了模板名称与错误名相同的,这里的TemplateAvailabilityProvider就是thymeleaf模板引擎;否则就是null   if (provider != null) {      return new ModelAndView(errorViewName, model);   }    //resolveResource方法会去静态资源目次下寻找;   return resolveResource(errorViewName, model);}private ModelAndView resolveResource(String viewName, Map model) {        for (String location : this.resources.getStaticLocations()) {                try {                        Resource resource = this.applicationContext.getResource(location);                        resource = resource.createRelative(viewName + ".html");                        if (resource.exists()) {                                return new ModelAndView(new HtmlResourceView(resource), model);                        }                }                catch (Exception ex) {                }        }        return null;}
复制代码
当DefaultErrorViewResolver返回的ModelAndView对象为null且没有其他错误视图剖析器时,意味着不存在我们自界说的错误视图;BasicErrorController的errorHtml方法最终会创建默认的error视图:new ModelAndView(“error”, model);
而名为error的视图springboot在ErrorMvcAutoConfiguration已经自动设置好了(whitelabel)。

来源:https://blog.csdn.net/bejsoiv/article/details/112024154
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

发布主题

专注素材教程免费分享
全国免费热线电话

18768367769

周一至周日9:00-23:00

反馈建议

27428564@qq.com 在线QQ咨询

扫描二维码关注我们

Powered by Discuz! X3.4© 2001-2013 Comsenz Inc.( 蜀ICP备2021001884号-1 )