关于java:从源码角度来看SpringMVC是如何处理HTTP请求的

9次阅读

共计 5045 个字符,预计需要花费 13 分钟才能阅读完成。

对于有教训的 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 的继承关系图,如下:

能够看到最上层的父类就是 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。有问题下方留言!

正文完
 0