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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【小家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
    }
}


相关文章
|
19天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
28 1
|
20天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
25 1
|
2月前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
203 24
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
1月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
16天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
21 0
|
1月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
53 2
|
1月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
167 2
|
2月前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
111 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
2月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向