【小家Spring】Spring MVC执行流程 FrameworkServlet、DispatcherServlet源码分析(processRequest、doDispatch)(上)

简介: 【小家Spring】Spring MVC执行流程 FrameworkServlet、DispatcherServlet源码分析(processRequest、doDispatch)(上)

前言


本篇博文会更偏向于应用、更加宏观一些。

但是,本人还是建议,在了解Spring MVC的执行流程之前,先参阅这两篇博文:

【小家Spring】Spring容器(含父子容器)的启动过程源码级别分析(含web.xml启动以及全注解驱动,和ContextLoader源码分析)

【小家Spring】Spring MVC初始化(容器启动)时,Spring MVC九大组件初始化详解(Spring MVC的运行机制)

这样会有助于你更好的了解本文的一个流程,毕竟既会宏观的从步骤上去讲解,也会稍微微观(注意是稍微)的从源码角度去分析一下子


备注:如果你已经是高手,已经对Spring的容器管理比较了解了,请自行忽略~


Spring MVC的执行流程似乎是一个面必问的话题,面试官能通过一个问题,探底出你掌握此部分只是的深度甚至广度,此篇博文,不容忽视啊,哈哈~


问题剖细了,可以是:一个请求url是怎么样找到Handler进行处理的?拦截器为何preHandler顺序执行,postHandler就倒序执行呢?Spring MVC是怎么样去优雅的处理异常的?…、


请求处理流程


了解之前,我们先宏观看看,一个请求达到Spring MVC,它的一个处理流程。

这里我首先贴上一张非常权威的流程图,也是Spring in Action这本书里提供的,springmvc的核心组件和请求处理流程


image.png


描述:


1.DispatcherServlet是springmvc中的前端控制器(front controller),负责接收request并将request转发给对应的处理组件


2.HanlerMapping是springmvc中完成url到controller映射的组件.DispatcherServlet接收request,然后从HandlerMapping查找处理request的controller


3.Cntroller处理request,并返回ModelAndView对象,Controller是springmvc中负责处理request的组件(类似于struts2中的Action),ModelAndView是封装结果视图的组件


4.④ ⑤ ⑥:视图解析器解析ModelAndView对象并返回对应的视图给客户端


我个人认为,这本书里说的还是稍微抽象了点,有核心,但是缺失了一些较为细节的处理步骤。那么下面,我也贴出一副更加具象的图片,供以参考:


image.png

描述:


1.用户发送的所有请求(包括上传附件等任何请求),统一先交给DispatcherServlet


2.然后DispatcherServlet调用合适的HandlerMapping ,从而找到一个Handler(Controller中的方法以及拦截器),然后封装成HandlerExecutionChain返回给控制器DispatcherServlet


3.调用处理器适配器HandlerAdapter去执行handler(注意:执行之前需要先请求执行链中的拦截器的preHandle方法进行拦截,返回true就继续执行,返回false就不继续执行了)


4.处理器执行完后,返回给控制器DispatcherServlet一个ModelAndView(里面放有视图信息,模型数据信息)。 然后就执行postHandle方法


5.控制器调用视图解析器解析视图,根据逻辑名(xxxx/xxxx/xxxx.jsp)解析成真正的视图view(jsp,ftl等),然后返给控制器一个View


6.控制器开始渲染视图(视图渲染器可以是第三方或自己实现),然后将模型数据填充到request中。


7.DispatcherServlet响应用户请求,展示jsp等视图信息



备注:这里面还会设计到数据绑定、序列化、返序列化、异常处理等一些内容,那就是更加细节的东西了,那就以后专题再讲解。毕竟这一块对使用者来说还是非常的透明的。。。


DispatcherServlet执行流程的源码分析


image.png


我们从调用栈了可以很直接的看到调用关系。

我发的请求为:http://localhost:8080/demowar_war/controller/hello GET请求。

FrameworkServlet复写了service方法如下:


  /**
   * Override the parent class implementation in order to intercept PATCH requests.
   * 官方doc说得很清楚,复写是为了支持到PATCH请求(PATCH方法是新引入的,是对PUT方法的补充,用来对已知资源进行局部更新,目前使用得非常少,但SpringMVC也给与了支持)
   * 备注:源生的servlet并不支持PATCH请求
   */
  @Override
  protected void service(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
      processRequest(request, response);
    }
    else {
      super.service(request, response);
    }
  }


因为我们是get请求,所以我们重点只需要看看子类复写的doGet方法即可。但是猛的发现,FrameworkServlet复写所有的doGet/doPost等等都交给了processRequest(request, response);方法。


doOptions稍微有点特殊,它处理一些是否允许跨域的问题,TRACE请求:主要用于测试或诊断,可忽略

FrameworkServlet#processRequest方法解析


该方法作为FrameworkServlet的实现,其实它也是提供了一些模版实现,最终会开口给子类的 模版设计模式,在Spring源码中大量存在。此处我们关注点在于:FrameworkServlet为我们做了哪些事情(相对来说比较复杂点)~


阅读前博文参考:

【小家Spring】Spring MVC之RequestContextHolder和LocaleContextHolder的使用详解以及使用误区


  protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    long startTime = System.currentTimeMillis();
    // 记录抛出的异常~~~(若有的话)
    Throwable failureCause = null;
    //拿到之前的LocaleContext上下文(因为可能在Filter里已经设置过了)
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    // 以当前的request创建一个Local的上下文,后面会继续处理
    LocaleContext localeContext = buildLocaleContext(request);
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    // 这里面build逻辑注意:previousAttributes若为null,或者就是ServletRequestAttributes类型,那就new ServletRequestAttributes(request, response);
    // 若不为null,就保持之前的绑定结果,不再做重复绑定了(尊重原创)
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    // 拿到异步管理器。这里是首次获取,会new WebAsyncManager(),然后放到request的attr里面
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    //这里需要注意:给异步上下文恒定注册了RequestBindingInterceptor这个拦截器(作用:绑定当前的request、response、local等)
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    //这句话很明显,就是吧request和Local上下文、RequestContext绑定
    initContextHolders(request, localeContext, requestAttributes);
    try {
      //模版设计模式:由子类DispatcherServlet去实现实际逻辑
      doService(request, response);
    } catch (ServletException | IOException ex) {
      failureCause = ex;
      throw ex;
    } catch (Throwable ex) {
      failureCause = ex;
      throw new NestedServletException("Request processing failed", ex);
    } finally { //这个时候已经全部处理完成,视图已经渲染了
      //doService()方法完成后,重置上下文,也就是解绑
      resetContextHolders(request, previousLocaleContext, previousAttributes);
      if (requestAttributes != null) {
        requestAttributes.requestCompleted();
      }
      //关键:不管执行成功与否,都会发布一个事件,说我处理了这个请求(有需要监听的,就可以监听这个事件了,每次请求都会有)
      publishRequestHandledEvent(request, response, startTime, failureCause);
    }
  }

publishRequestHandledEvent()发布请求处理完后的事件源码

  private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,
      long startTime, @Nullable Throwable failureCause) {
    //当publishEvents设置为true和 webApplicationContext 不为空就会处理这个事件的发布
    if (this.publishEvents && this.webApplicationContext != null) {
      // 计算出处理该请求花费的时间
      long processingTime = System.currentTimeMillis() - startTime;
      this.webApplicationContext.publishEvent(
          //ServletRequestHandledEvent这个事件:目前来说只有这里会发布
          new ServletRequestHandledEvent(this,
              request.getRequestURI(), request.getRemoteAddr(),
              request.getMethod(), getServletConfig().getServletName(),
              WebUtils.getSessionId(request), getUsernameForRequest(request),
              processingTime, failureCause, response.getStatus()));
    }
  }


下面我们来写个监听器,专门来监听这个事件:


/**
 * 专门监听ServletRequestHandledEvent时间的监听器
 *
 * @author fangshixiang
 * @description //
 * @date 2019/2/28 12:10
 */
@Slf4j
@Component
public class ServletReqestHandledEventListener implements ApplicationListener<ServletRequestHandledEvent> {
    @Override
    public void onApplicationEvent(ServletRequestHandledEvent event) {
        //url=[/demowar_war/controller/hello]; client=[127.0.0.1]; method=[GET]; servlet=[dispatcher]; session=[null]; user=[null]; time=[143ms]; status=[OK]
        log.info(event.getDescription()); 
        log.info("返回状态码为:" + event.getStatusCode()); //返回状态码为:200
        log.info("异常信息为:" + event.getFailureCause()); //异常信息为:null
        log.info("处理请求耗时为:" + event.getProcessingTimeMillis()); //处理请求耗时为:143
        log.info("事件源为:" + event.getSource()); //事件源为:org.springframework.web.servlet.DispatcherServlet@3e7fadbb
    }
}


相关文章
|
1月前
|
缓存 前端开发 Java
Spring MVC 面试题及答案整理,最新面试题
Spring MVC 面试题及答案整理,最新面试题
91 0
|
1月前
|
XML 前端开发 Java
深入了解Spring MVC工作流程
深入了解Spring MVC工作流程
|
16天前
|
数据采集 前端开发 Java
数据塑造:Spring MVC中@ModelAttribute的高级数据预处理技巧
数据塑造:Spring MVC中@ModelAttribute的高级数据预处理技巧
23 3
|
16天前
|
存储 前端开发 Java
会话锦囊:揭示Spring MVC如何巧妙使用@SessionAttributes
会话锦囊:揭示Spring MVC如何巧妙使用@SessionAttributes
14 1
|
16天前
|
前端开发 Java Spring
数据之桥:深入Spring MVC中传递数据给视图的实用指南
数据之桥:深入Spring MVC中传递数据给视图的实用指南
31 3
|
25天前
|
前端开发 安全 Java
使用Java Web框架:Spring MVC的全面指南
【4月更文挑战第3天】Spring MVC是Spring框架的一部分,用于构建高效、模块化的Web应用。它基于MVC模式,支持多种视图技术。核心概念包括DispatcherServlet(前端控制器)、HandlerMapping(请求映射)、Controller(处理请求)、ViewResolver(视图解析)和ModelAndView(模型和视图容器)。开发流程涉及配置DispatcherServlet、定义Controller、创建View、处理数据、绑定模型和异常处理。
使用Java Web框架:Spring MVC的全面指南
|
1月前
ssm(Spring+Spring mvc+mybatis)Dao层实现类——DeptDaoImpl
ssm(Spring+Spring mvc+mybatis)Dao层实现类——DeptDaoImpl
12 0
|
1月前
ssm(Spring+Spring mvc+mybatis)Dao接口——IDeptDao
ssm(Spring+Spring mvc+mybatis)Dao接口——IDeptDao
8 0
|
1月前
ssm(Spring+Spring mvc+mybatis)实体类——Dept
ssm(Spring+Spring mvc+mybatis)实体类——Dept
13 0
|
1月前
|
Java 关系型数据库 MySQL
ssm(Spring+Spring mvc+mybatis)
ssm(Spring+Spring mvc+mybatis)
13 0