HTTP请求的流转路径,从Tomcat到SpringMVC

本文主要讲一下,一个HTTP请求在后端服务的流转路径,Tomcat等一众servlet容器如何定义了Web应用的基础样貌,后来的MVC框架又是如何弱化了servlet的存在,改为自己实现请求派发的。

前些日子我写了十几篇文章来介绍Tomcat的架构,并汇总了一个文章目录:Tomcat文章目录,如果你对Tomcat也感兴趣,可以翻看一下。Tomcat整体架构分为两大块:连接器与容器。连接器负责接收HTTP请求并将其解析为Request对象,然后连接器将Request对象传给容器,四大容器会层层向下寻址子容器,直到找到该请求对应的servlet。

在不谈MVC框架的情况下,一个HTTP请求在Tomcat中是这样流转的

  1. 连接器收到HTTP请求,从"处理线程"的线程池中挑一个线程,将该请求对应的socket连接抛给该线程进行接下来的处理,而连接器主线程则继续等待下一个请求。
  2. 处理线程将HTTP请求拼装成容器需要的Request与Response对象,将两个对象抛给容器进行接下来的处理。
  3. Tomcat中的容器都是一对多的关系,每个父容器都有自己的策略,可以根据request请求来定位到某个子容器。从Engine容器接收到请求开始,就开始层层往下寻找子容器,直到找到一个Wrapper容器,该Wrapper容器包含了能处理该request请求的servlet,接着就调用该servlet的service方法来执行用户自定义逻辑处理该request请求,并拿到结果数据。最后将返回结果层层上抛,处理线程拿到容器给的结果后,回传给客户端,一个HTTP请求的一生就结束了。

所以按照这个流程来说,一个Web应用中应该存在很多个不同的servlet来处理对应的HTTP请求,(这里我暂且叫这种模式为多servlet模式),在servlet编程时代,确实也是这个样子。

但是当MVC框架出来后,这个模式发生了一些改变,MVC框架放弃了多servlet模式,转而用一个servlet来接收所有的HTTP请求,在自己的框架内部再进行请求的分派处理。

MVC为什么这么做呢?我理解MVC这么做是为了减少与Tomcat的耦合度,转为在自身框架内部实现高度的定制化;而且使用单servlet天然的就将请求入口统一了,如果要对请求做一些统一处理就变得很方便。

MVC框架有哪些呢?最初大家使用Struts比较多,在Struts漏洞暴雷后,现在大家基本上都在使用SpringMVC,而且SpringBoot普及后,SpringMVC就成了一个理所应当的选择。接下来就简单看下SpringMVC的设计。

DispatcherServlet

在SpringMVC中那个入口servlet就是DispatcherServlet,它负责配合Tomcat来接收请求。

HandlerMethod

SpringMVC内部怎么表达一个Controller方法呢?答案就是用一个HandlerMethod对象来表示一个Controller方法。

例如下面这个Controller,testGet 与 testPost 两个方法在SpringMVC内部就会被表示成两个 HandlerMethod 对象

java 复制代码
@RestController
public class TestController {

    @GetMapping("/test")
    public User testGet(Long userId) {
        User user = new User();
        user.setUserId(userId);
        user.setName("张三");
        user.setAge(18);
        return user;
    }

    @PostMapping("/test")
    public User testPost(Long userId) {
        User user = new User();
        user.setUserId(userId);
        user.setName("张三");
        user.setAge(18);
        return user;
    }

    @Data
    private static class User{
        private Long userId;
        private String name;
        private Integer age;
    }

}

HandlerMethod中存放了该方法的名字,所在类,参数信息,对应的请求uri,以及支持的请求方式(get、post...)等信息。

SpringMVC在启动时就将所有的 HandlerMethod 对象创建好,并为其建立一个索引 <uri,HandlerMethod对象> ,我们将这个索引称为 HandlerMapping。通过 HandlerMapping 就可以根据请求找到指定的 HandlerMethod 。

找到了 HandlerMethod 怎么执行它代表的方法呢?老套路,使用反射,即 method.invoke() 方法。这个反射执行逻辑被放在了一个叫 HandlerAdapter 的组件里。

加入拦截器

在反射执行 HandlerMethod 的方法前,会找到项目中配置的所有与这个请求有关的拦截器,然后在反射调用 method 前后,执行拦截器中的 preHandle、postHandle 及 afterCompletion 方法,拦截器的工作模式就是这样。拦截器的接口定义为 HandlerInterceptor 。

另外,Tomcat传给 DispatcherServlet 的信息为 HttpServletRequest 与 HttpServletResponse两个对象,SpringMVC内部拥有将 HttpServletRequest 转化为 method 的参数对象的组件,也有将method 的返回结果转化为 json 等结构的组件。(这里的method指的就是HandlerMethod对应的method)。

现在,我们再来看下Tomcat与SpringMVC配合时,一个HTTP请求的一生。

HTTP请求在Tomcat中的流转路径不变,最后找到 DispatcherServlet 这个servlet,接下来就进入到了SpringMVC的框架范围了。

SpringMVC根据请求的 HttpServletRequest 对象,找到对应的 HandlerMethod, 再找到会拦截该HanderMethod的拦截器,执行对应拦截器的 preHandle 方法后,再反射调用 HandlerMethod 对应的 method 方法,拿到反射调用的结果后,再按照程序安排执行拦截器的 postHandle 、afterCompletion 方法,拿到最终结果后再抛给Tomcat,Tomcat将结果返回给客户端。

另外在SpringMVC中还存在全局异常处理等机制,代码入口在 DispatcherServlet 的 processDispatchResult 方法中,通过 @RestControllerAdvice 配置的自定义全局异常处理机制就会在这个方法里被触发,感兴趣的自己去翻一下吧。

相关推荐
手心里的白日梦1 小时前
UDP传输层通信协议详解
网络·网络协议·udp
红米饭配南瓜汤1 小时前
WebRTC服务质量(11)- Pacer机制(03) IntervalBudget
网络·网络协议·音视频·webrtc·媒体
fat house cat_2 小时前
Linux环境下使用tomcat+nginx部署若依项目
linux·nginx·tomcat
Xiaoweidumpb2 小时前
tomcat temp临时文件不清空,占用硬盘,jdk字体内存泄漏
java·tomcat
小马爱打代码3 小时前
Tomcat整体架构分析
java·架构·tomcat
m0_548503033 小时前
【Java Web】Tomcat 快速入门
java·前端·tomcat
萧瑟其中~4 小时前
计算机网络:TCP/IP网络协议
网络协议·tcp/ip·计算机网络
记得开心一点嘛7 小时前
Nginx与Tomcat之间的关系
java·nginx·tomcat
梦境之冢8 小时前
axios 常见的content-type、responseType有哪些?
前端·javascript·http
DashVector8 小时前
如何通过HTTP API检索Doc
数据库·人工智能·http·阿里云·数据库开发·向量检索