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 配置的自定义全局异常处理机制就会在这个方法里被触发,感兴趣的自己去翻一下吧。

相关推荐
en-route2 小时前
HTTP 缓存
网络协议·http·缓存
一只小鱼儿吖10 小时前
进程代理单窗口单IP技术:原理、应用与实现
网络·网络协议·tcp/ip
稳联技术11 小时前
Ethernet IP与Profinet共舞:网关驱动绿色工业的智慧脉动
网络·网络协议·tcp/ip
隆里卡那唔11 小时前
在dify中通过http请求neo4j时为什么需要将localhost变为host.docker.internal
http·docker·neo4j
高兴达11 小时前
RPC框架--实现一个非常简单的RPC调用
网络协议·rpc·firefox
~山有木兮12 小时前
LiteHub中间件之限流实现
网络·http·中间件
ye9013 小时前
银河麒麟V10服务器版 + openGuass + JDK +Tomcat
java·开发语言·tomcat
游戏开发爱好者813 小时前
iOS App首次启动请求异常调试:一次冷启动链路抓包与初始化流程修复
websocket·网络协议·tcp/ip·http·网络安全·https·udp
2501_9151063213 小时前
频繁迭代下完成iOS App应用上架App Store:一次快速交付项目的完整回顾
websocket·网络协议·tcp/ip·http·网络安全·https·udp
cocologin14 小时前
RIP 技术深度解析
运维·网络·网络协议