1

对于有经验的SpringMVC使用人员来说,应该大致了解它对HTTP请求的处理流程,SpringMVC是对原生的Servlet的一种扩展,里面对请求的方法及处理请求的方法做了映射,也就是我们常见的@RequestMapping中指定的路径,以及带有@RequestMapping注解的方法的映射。这个映射是在SpringMVC容器启动的时候预解析并初始化到Spring容器中的,他们保存在一个Map中,Key可以简单理解为请求路径,Value可以简单理解为请求处理器,也就是对应的controller方法。请求来了之后,则根据请求uri获取对应的处理器方法,然后反射调用。

那么SpringMVC的细节处理流程又是什么呢?我们下面会逐步从源码来了解其细节:

首先是初始化的过程,如何在Spring容器启动过程中去初始化控制器Controller和请求映射器RequestMapping,这个初始化流程完成之后,产生的结果就是将请求映射RequestMapping和处理器方法的对应关系注册到请求映射器注册中心MappingRegistry中。那么当http请求到达SpringMVC框架之后,它是如何来接收请求的呢?

下面来分析一下!

首先在分析之前,大家需要有一个基础知识储备,就是Servlet的执行流程,我们都知道SpringMVC框架是遵循Servlet规范,通过Servlet来扩展出来的,那么他的执行流程也一定离不开原生的Servlet。

在Servlet容器「通常指Tomcat」中,每当Http请求到达Servlet容器之后,都会执行Servlet的service方法,那执行Servlet的service方法和SpringMVC执行流程有什么关系呢?

我们首先来看看SpringMVC的核心处理器DispatcherServlet的继承关系图,如下:
image.png
可以看到最上层的父类就是Servlet的子类实现HttpServlet,那么来看看这个service方法是在哪调用的呢?

按照之前介绍的技巧,可以从最底层的子类一个一个往上找,看看这个init方法是在哪个类中调用的,找完之后,发现是在FrameworkServlet中调用的,如下:

/**
 * 重写了父类的方法,增加对PATCH请求方式的处理
 */
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) {
  // 获取请求方法类型。将请求方法类型转换为HttpMethod枚举类型
  HttpMethod httpMethod = HttpMethod.resolve(req.getMethod());

  // 如果是PATCH类型,直接走processRequest方法。
  // PATCH请求:PUT请求方法是用来替换资源的,而PATCH方法是用来更新部分资源的.
  if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
    processRequest(req, resp);
  }
  else {
    // 调用父类HttpServlet的service方法,父类方法中会判断请求类型是get还是post,从而去调用子类的doGet或者doPost等方法
    super.service(req, resp);
  }
}

第一步首先获取http请求的请求类型,可以稍微了解了解这个方法,根据方法名称从一个Map集合中获取请求类型,可以看到这个枚举类中有如下的8种枚举类型,也就是HTTP的请求方式有8种。

public enum HttpMethod {
  GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
}

如果是PATCH类型的请求,直接走processRequest方法,那么这个PATCH请求是个什么玩意儿呢?

我们都知道,RestFULL设计规范中,PUT请求是用来替换资源的,而这个PATCH请求则是用来更新部分资源的。

接着看super.service方法,如果不是PATCH请求而且Method存在的话,就会执行这个方法,这个方法点开之后,直接调用的是HttpServlet的service方法,这个方法中会根据请求类型POST或者GET等来决定调用doGet、doPost还是其他类型的方法。可以看到,此处是用来了分派设计模式。

这个方法中,假设调用的是一个GET方式请求的接口,那么就会调用doGet方法,而这个doGet方法又被子类FrameworkServlet覆写了,所以会调用到FrameworkServlet的doGet方法,我们来看看FrameworkServlet的doGet方法,如下:

/**
 * 处理get请求,异常省略掉了
 */
@Override
protected final void doGet(HttpServletRequest req, HttpServletResponse resp) {
  // 处理get请求
  processRequest(req, resp);
}
里面直接调用了processRequest方法,和刚才上面看到的处理PATCH请求调用的方法一样,那么继续分析processRequest方法,这个方法的核心代码如下:
/**
 * 处理具体的http请求,异常省略掉
 */
protected final void processRequest(HttpServletRequest req, 
      HttpServletResponse resp) {
  // 中间处理国际化和异步调用的代码暂时先省略掉了
  try {
    // 调用doService方法来处理请求
    doService(req, resp);
  }
  catch (Throwable ex) {
    failureCause = ex;
    throw new NestedServletException("Request processing failed", ex);
  }
  finally {
    // 重置ContextHolder
    ...
  }
}

可以看到,核心就是调用了一个doService方法。我们在看doService方法的实现,发现它是一个抽象方法,没有实现,有一次看到了模板设计模式的应用了吧?我们找其子类中对这个方法的实现,发现是在DispatcherServlet中实现的,核心代码如下:

@Override
protected void doService(HttpServletRequest req, HttpServletResponse resp) {
  // 打印请求日志
  logRequest(req);
  // 设置了一堆解析器,也就是Spring的九大组件
  //  可以看到,设置到request中,后续用到,直接从request中获取
  req.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
  req.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
  req.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
  req.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
  if (this.flashMapManager != null) {
    req.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
  }
  try {
    // 调用doDispatch方法来处理请求,SpringMVC用来处理请求的核心方法.
    doDispatch(req, resp);
  }
  finally {
    // 省略
  }
}

核心代码还是一行,即调用doDispatch方法,这个方法就到SpringMVC真正的核心处理方法了,所有的处理逻辑都是在这个方法中完成的,包括文件上传、拦截器、异常处理器等。我们先看核心处理请求的脉络,具体的其他功能可以慢慢了解,核心逻辑代码如下,下面代码中为了看着清晰,删除了一些校验和变量定义相关的代码:

protected void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
  HttpServletRequest processedRequest = req;
  // 处理器执行链
  HandlerExecutionChain mappedHandler = null;
  boolean multipartRequestParsed = false;
  try {
    try {
      // 检查请求中是否有二进制流,用来判断请求是否为文件上传请求。
      //  如果容器中没有配置MultipartResolver组件,则直接忽略文件上传请求
      processedRequest = checkMultipart(req);

      // 需要处理文件上传请求
      multipartRequestParsed = (processedRequest != req);

      // 根据请求查找请求处理器
      // 声明一个controller通常有三种方法:
      // (1)@Controller 
      // (2)实现HttpRequestHandler接口,并覆写handleRequest方法,
            将controller类通过@Component标注为Bean
      // (3)通过xml配置.
      mappedHandler = getHandler(processedRequest);

      // 如果没有找到对应的handler,直接返回404
      if (mappedHandler == null) {
        noHandlerFound(processedRequest, resp);
        return;
      }
      // 查找当前请求处理器对应的适配器,通常使用的是RequestMappingHandlerAdaptor,其他两种Adaptor一般不常用
      HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
      // 处理请求之前,执行拦截器中配置的方法,获取到配置的所有拦截器,循环遍历,依次执行
      //  如果拦截器执行过程中返回了false,则直接返回,不会再执行目标方法
      if (!mappedHandler.applyPreHandle(processedRequest, resp)) {
        return;
      }
      // 执行SpringMVC中真实对应的业务方法
      //   HandlerAdapter的实现子类有:AbstractHandlerMethodAdapter、HttpRequestHandlerAdapter
      mv = ha.handle(processedRequest, resp, mappedHandler.getHandler());
      // 查找视图路径. prefix + uri + suffix
      applyDefaultViewName(processedRequest, mv);
      // 处理请求之后执行拦截器方法.
      mappedHandler.applyPostHandle(processedRequest, resp, mv);
    }
    catch (Exception ex) {
      dispatchException = ex;
    }
    // 将处理完成之后的结果进行页面视图渲染。比如:跳转到Jsp页面.
    processDispatchResult(processedRequest, resp, mappedHandler, mv, dispatchException);
  }
  catch (Exception ex) {
    // 页面渲染过程中,如果出现异常,则会直接执行afterCompletion方法
    triggerAfterCompletion(processedRequest, resp, mappedHandler, ex);
  }
  finally {
    // 一些善后工作,例如清理文件上传留下来的临时文件等
  }
}

上述方法执行完成之后,SpringMVC的一次请求就处理完成了。由于篇幅原因,具体的文件上传判断、视图渲染返回、异常处理、拦截器执行逻辑后面文章持续介绍。

带有注释的源码已经上传到github,地址:https://github.com/wb02125055...

可以自行fork。有问题下方留言!


夏日寒冰
318 声望85 粉丝

忠实的技术爱好者,追求极致,喜欢总结一些自己用过的技术点,与他人交流分享。