tomcat + spring mvc原理(八):spring mvc对请求的处理流程

tomcat + spring mvc原理(八):spring mvc对请求的处理流程

前言

    原理七讲了spring mvc整体的初始化、DispatcherServlet的继承结构和spring mvc组件的初始化。网站服务器最重要的还是提供网络服务,所以这期主要讲spring mvc是如何处理和传递请求的。
    在原理五的最后,有提到:

在所有注册的Filter都遍历一遍之后,FilterChain会调用Servlet的service方法处理请求,自此,请求正式进入到spring mvc的领域。

所以这里会先从service方法讲起。

FrameworkServlet对请求的处理

    DispatcherServlet中并没有重载service,上文中的Servlet.service方法的实现是在DispatcherServlet的父类FrameworkServlet中。

//FrameworkServlet.java
@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);
  }
}

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
  processRequest(request, response);
}

FrameworkServlet和原理六介绍的HttpServlet(也是FrameworkServlet的父类)的理念完全不同。HttpServlet基本思想是将收到的http请求进行分类,然后分发处理:

HttpServlet继承GenericServlet,这个Servlet和协议相关,主要包括了一些用来处理http请求的方法,包括doHead、doGet、doPost、doPut、doDelete、doOptions、doTrace等,当然还有service方法。service()作为请求的通用入口,然后根据Http请求的类型不同分发给doXxx()方法。

FrameworkServlet的理念完全相反,service的重载以及doGet、doPost等方法的重载实现统一合并到processRequest方法处理请求。这里可能会有疑问,为什么FrameworkServlet不在service方法中直接所有请求都走processRequest方法,而是除了处理PATCH请求之外,其他请求还是调用父类HttpServlet的service方法,然后在doGet、doPost等方法的重载中再次使用processRequest方法来处理呢?这种舍近求远的做法可能是为了扩展性。如果用户想要继承DispatcherServlet,实现建立在DispatcherServlet基础上的自己的Servlet,如果想要重载doGet、doPost等方法,在请求传递到下一级之前对请求进行一些处理,接着再调用super.doGet或者super.doPost方法。如果FrameworkServlet没有实现这些方法,整个调用链路就断了。这应该是采用这种曲折的重载方式的原因。
    总之,这样所有请求类型都又重新汇总,统一由FrameworkServlet的processRequest方法处理。这样大家可能还是会有疑问:那么这样做,不同类型的请求要如何分类处理呢?spring mvc的实现方式是在后续请求的处理中使用不同的Handler来处理不同请求。
    processRequest做了三件事情:

  1. 调用doService方法,传递请求和应答体。
  2. 对LocaleContext的设置和恢复。
  3. 对RequestAttributes的设置和恢复。
  4. 发布ServletRequestHandledEvent消息。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

  long startTime = System.currentTimeMillis();
  Throwable failureCause = null;
  //获取LocaleContext原有配置
  LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
  //获取当前请求的LocaleContext的配置
  LocaleContext localeContext = buildLocaleContext(request);
  //获取RequestAttributes原有配置
  RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
  //获取当前请求的RequestAttributes配置
  ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

  WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
  //设置为当前请求的LocaleContext配置和RequestAttributes配置
  initContextHolders(request, localeContext, requestAttributes);

  try {
    //调用doService模板方法
    doService(request, response);
  }
  catch (ServletException | IOException ex) {
    failureCause = ex;
    throw ex;
  }
  catch (Throwable ex) {
    failureCause = ex;
    throw new NestedServletException("Request processing failed", ex);
  }

  finally {
    //恢复LocaleContext和RequestAttributes为原有配置
    resetContextHolders(request, previousLocaleContext, previousAttributes);
    if (requestAttributes != null) {
      requestAttributes.requestCompleted();
    }
    logResult(request, response, failureCause, asyncManager);
    publishRequestHandledEvent(request, response, startTime, failureCause);
  }
}

    调用doService方法是processRequest的方法的核心,不过doService是在子类DispatcherServlet中重载实现的。LocaleContext用来存储本地的服务器的相关配置,比如系统语言,如图调试显示的结果:
在这里插入图片描述
RequestAttributes存储了请求属性相关的东西,还包括请求体request、应答体response、会话session的实例。这些属性、配置都存储在当前环境中,可以通过Holder获取。也就是说,在处理请求的过程中RequestContextHolder能够获取所有和请求相关的内容,大家在业务代码编写中也可以使用LocaleContextHolder和RequestContextHolder这两个Holder。但是需要注意,在处理完请求之后,这个RequestAttributes会被关闭(内部定义了requestActive标识),然后环境设置会被重置。这些配置都是是线程相关的,使用TheadLocal<>容器进行存储。
    在请求处理完后,无论成功与否,除了配置恢复,还会发布一个请求处理完成的事件ServletRequestHandledEvent。在spring mvc的web.xml文件中配置publishEvents可以控制这个事件是否会被发送,默认设置为true。可以实现ApplicationListeners接口来监听这个事件,定制自己的请求结束后收尾代码。

DispatcherServlet对请求的处理

    DispatcherServlet在处理请求的过程中使用了各种组件相互协作,统帅了整个流程,是spring mvc中非常重要的一个类。原理七中对DispatcherServlet及其组件的初始化做了一个简单的介绍,现在来研究一下它是如何处理请求的。
    请求进入DispatcherServlet的方法是doService。doService方法中又用doDispatch方法进行具体的请求处理。doService方法中主要做的工作包括打印日志、判断是否include请求、属性设置等。

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
  //1.打印日志工作
  logRequest(request);
  //2.判断是否是include请求
  Map<String, Object> attributesSnapshot = null;
  if (WebUtils.isIncludeRequest(request)) {
    attributesSnapshot = new HashMap<>();
    Enumeration<?> attrNames = request.getAttributeNames();
    while (attrNames.hasMoreElements()) {
      String attrName = (String) attrNames.nextElement();
      if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
        attributesSnapshot.put(attrName, request.getAttribute(attrName));
      }
    }
  }
  //3.设置属性
  request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
  request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
  request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
  request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

  if (this.flashMapManager != null) {
    FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
    if (inputFlashMap != null) {
      request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
    }
    request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
    request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
  }

  try {
    //4.调用doDispatch
    doDispatch(request, response);
  }
  finally {
    //5. 如果是include请求,还原属性
    if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
      if (attributesSnapshot != null) {
        restoreAttributesAfterInclude(request, attributesSnapshot);
      }
    }
  }
}

在处理请求的过程中,需要多个Servlet协作才能完成。跨多个Servlet需要设置为include请求或者forward请求,forword跳转比较常见,是由下一个Servlet完成响应,比较类似于迭代过程。但是include请求是在请求的过程中包含了对其他Servlet的请求,最后需要回到当前Servlet请求流程中来,比较类似于递归过程,所以需要重新设置属性。这就是为什么include请求需要额外处理的原因。
    在处理include请求保存属性之后,doService()对请求体request设置了一些属性。如果你看了之前的文章,应该会对webApplicationContext有点眼熟。原理七中有提到:

所谓的webApplicationContext包含了很多重要的东西,比如类加载的ClassLoader、Context配置的ServletContext、Servlet配置的ServletConfig、tomcat的webServer实例、注解相关的AnnotatedBeanDefinitionReader、BeanFactory实例、资源加载的resourceLoader等等。所以initWebApplicationContext()中使用了多重机制,确保能够将这个环境的配置获取。

至于localeResolver、themeResolver和themeSource也会有点眼熟,这和后面讲的handler和view相关,这里不多说。然后就是INPUT_FLASH_MAP_ATTRIBUTE、OUTPUT_FLASH_MAP_ATTRIBUTE、FLASH_MAP_MANAGER_ATTRIBUTE这三个属性都是和redirect重定向相关的。FlashMap主要就是用来做重定向过程中参数的传递,这个组件的会在后面仔细讲解,这里也不再赘述。
    DispatcherServlet的doService方法使用了一个FlashMap的组件,doDispatch方法涉及的组件就比较多了。为了能够对整个流程有清晰的理解,这里先介绍MVC设计模式的理念。MVC是model、view和controller的简写,model是数据模型,view就是常见的前端或者客户端展示的视图,Controller定义用户界面对于输入的相应方式,对应到web服务领域,Controller就是主要处理请求的逻辑。目前业务项目中Controller在工程中比较常见,因为项目实现一般采用前后端分离模式,后端不负责前端的解析和渲染,所以model和view就不太常接触了。如果项目前端采用的thymeleaf,解析使用自带的Thymeleaf视图解析器,就会对这个两个概念更加清楚。简单来说,以excel程序为例,view就是眼前所见的表格视图,model就是表格中的数据模型,但是数据模型可以对应不同的视图,比如表格,比如柱状图。controller就用来处理用户输入的消息或者请求。
    在spring mvc中,Handler就是处理器,也就是spring mvc的c,Controller层,由@RequestMapping 或者@GetMapping或者@PostMapping注解的方法都是Handler。和Handler相关的还有HandlerMapping和HandlerAdapter。HandlerMapping用来查找用来处理一个请求的Handler。HandlerAdapter用来适配Handler处理相应的请求。为什么Handler需要适配器协助处理请求呢?这里卖个关子,具体可以看后续的组件专题。然后是View和ViewResolver,它们之间的关系类似于Handler和HandlerMapping, ViewResolver用来查找合适视图来展示请求的结果。
    现在来具体分析下doDispatch的代码。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  HttpServletRequest processedRequest = request;
  HandlerExecutionChain mappedHandler = null;
  boolean multipartRequestParsed = false;

  WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

  try {
    ModelAndView mv = null;
    Exception dispatchException = null;

    try {
      //检查是不是上传类型的请求
      processedRequest = checkMultipart(request);
      multipartRequestParsed = (processedRequest != request);

      //1. 查找获取对应请求的Handler
      mappedHandler = getHandler(processedRequest);
      if (mappedHandler == null) {
        noHandlerFound(processedRequest, response);
        return;
      }
      //2.确定相应Handler的HandlerAdapter
      HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

      // 处理GET和POST请求的Last-Modified的属性
      String method = request.getMethod();
      boolean isGet = "GET".equals(method);
      if (isGet || "HEAD".equals(method)) {
        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
          return;
        }
      }
      //3.调用Intercepter和preHandler
      if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
      }

      // Handler处理请求,由HandlerAdapter主导
      mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

      //异步处理的消息,就直接返回了
      if (asyncManager.isConcurrentHandlingStarted()) {
        return;
      }
      //使用默认视图处理结果
      applyDefaultViewName(processedRequest, mv);
      //4.调用Intercepter和postHandler
      mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    catch (Exception ex) {
      dispatchException = ex;
    }
    catch (Throwable err) {
      //这里主要是4.3版本兼容报错的处理,@ExceptionHandler注解的方法或者其他场景
      dispatchException = new NestedServletException("Handler dispatch failed", err);
    }
    //5.处理异常、渲染页面、触发Interceptor的afterCompletion,做请求结果的收尾
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  }
  catch (Exception ex) {
    triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  }
  catch (Throwable err) {
    triggerAfterCompletion(processedRequest, response, mappedHandler,
        new NestedServletException("Handler processing failed", err));
  }
  finally {
    //异步请求是否需要执行
    if (asyncManager.isConcurrentHandlingStarted()) {
      // Instead of postHandle and afterCompletion
      if (mappedHandler != null) {
        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
      }
    }
    else {
      // 清除上传请求的资源
      if (multipartRequestParsed) {
        cleanupMultipart(processedRequest);
      }
    }
  }
}

    mappedHandler = getHandler(processedRequest),返回的mappedHandler是HandlerExecutionChain类型,内含Interceptor和Handler。和Controller一样,Interceptor也被归类为一种Handler。接着,getHandlerAdapter方法获取了Handler对应的HandlerAdapter。
    Last-Modified属性是用来标记请求资源是否过期的。服务器返回的应答中包含Last-Modified属性,标记资源最后的修改时间,浏览器再次请求会发送这个修改时间,服务器对比当前的服务器上修改时间,就能够判断是否需要返回新资源,如果不需要则直接返回304,浏览器收到304后就会使用之前缓存的资源。
    接下来的流程就是Interceptor.prehandle处理请求->HandlerAdapter配合Handler处理请求-执行Interceptor的postHandle->最后请求返回的结果,有异常处理异常,没异常渲染页面、触发Interceptor的afterCompletion(processDispatchResult方法中)。
    最后对processDispatchResult方法作一下解释。processDispatchResult方法处理异常用到了HandlerExceptionResolver组件,获得异常的ModelAndView对象,然后错误页面设置到View中,比较常见的错误页面就类似这个:
在这里插入图片描述processDispatchResult方法渲染页面的功能在render方法中事实现,其中使用到了LocaleResolver获取配置,使用ViewResolver传入Locale配置和viewName获取了View(resolveViewName方法中),然后调用View的render方法渲染页面,渲染页面时使用了ThemeResolver。最后,执行mappedHandler的triggerAfterCompletion完成触发Interceptor的afterCompletion方法的任务。

本系列文章:
tomcat + spring mvc原理(一):tomcat原理综述和静态架构
tomcat + spring mvc原理(二):tomcat容器初始化加载和启动
tomcat + spring mvc原理(三):tomcat网络请求的监控与处理1
tomcat + spring mvc原理(四):tomcat网络请求的监控与处理2
tomcat + spring mvc原理(五):tomcat Filter组件实现原理
tomcat + spring mvc原理(六):tomcat WAR包的部署与加载
tomcat + spring mvc原理(七):spring mvc的Servlet和九大标准组件的静态结构与初始化

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值