本系列文章皆在分析SpringMVC
的核心组件和工作原理,让你从SpringMVC
浩如烟海的代码中跳出来,以一种全局的视角来重新审视SpringMVC
的工作原理.
作者:毅航
在过去的很长一段时间内,笔者对SpringMVC
中的核心控制器DispatcherServlet
中doDispatch
的处理逻辑进行了详细的分析,并以doDispatch
的执行逻辑为切入点,详细的拆解了SpringMVC
中的MultipartResolve(上传请求处理器)、HandlerMapping(处理器映射器)、HandlerInterceptor(拦截器)、HttpMessageConverter(消息转换器)
等组件信息。更详细的内容可参考专栏:SpringMVC流程分析[1]。
在专栏的前几章中我们主要介绍了SpringMVC
内部在处理Http
请求过程中会需要一些组件,毫无疑问这些组件
在处理Http
请求过程中发挥着至关重要的作用,但某种程度上来说其可能有一些太过于细节,读完后你可能只是搞懂了 SpringMVC
中某些组件
的作用以及作用的时机。 同时,你可能会面临这样一个问题,就是 似乎
自己搞懂了很多SpringMVC
的组件
,但却很难将其串起来,仿佛每个知识都是孤立的。
针对这一问题,笔者今天想从更加宏观角度来谈一谈一个HTTP
请求在SpringMVC
内部处理的全过程。以方便读者更加深入理解专栏SpringMVC流程分析[2]的内容,所以本文可以算作是对前几章内容的一个回顾和总结
。
1前言
在当下这个时代,我们每天都会借助浏览器
浏览很多内容。但你是否有考虑过当你在浏览器中访问某一个网址时候,这背后都发生了那些事情呢?
事实上,当在浏览器
中键入url
后,其背后的处理逻辑可大致如下图所示:
可以看到,键入url
后其大致会经历如下几个步骤:
-
DNS
解析: 首先,浏览器会解析网址中的主机名,以获取服务器的IP
地址。这个过程通过DNS
(域名系统)完成。 -
建立
TCP
连接: 一旦浏览器知道了服务器的IP
地址,它会尝试建立到服务器的TCP
连接。通常这个过程会包括三次握手,以确保客户端和服务器之间的连接建立成功。 -
发送
HTTP
请求: 一旦TCP
连接建立,浏览器会发送一个Http
请求到服务器。这个请求包括了要访问的资源路径、HTTP
方法(GET、POST
等)、请求头(包含用户代理、接受的数据类型等)以及任何附加数据(例如,表单数据或请求体)。 -
服务器处理请求: 服务器收到
HTTP
请求后,会根据请求的路径和方法来确定如何处理请求。这通常涉及到后端应用程序的控制器(例如,SpringMVC的Controller
)处理请求,执行相应的业务逻辑。 -
生成
HTTP
响应,发送响应: 一旦服务器完成请求处理,它会生成一个Http
响应。而这个响应通常包括:状态码、响应头以及响应体。 -
浏览器渲染: 浏览器接收到
HTTP
响应后,根据响应的内容类型(例如,HTML、CSS、JavaScript
等)来渲染页面。
实际上Http
请求的处理流程会涉及到很多复杂细节,笔者在此也只是做一个抛砖引玉的介绍,其主要目的也是在于让读者对于Http
请求的大致处理过程有一个大致的认识。
2简化Http
请求的处理流程
虽然上述已经对Http
请求的处理流程进行了极大的简化,但可能你还是会觉得上述对于Http
请求处理的有些繁琐。那能不能对上述步骤
再进行一层抽象,让其更容易理解呢?答案当然是肯定的!
基于此,我们对上图所示的Http
请求处理流程进行再一次抽象
,得到下图所示内容:
可以看到当对复杂事物进行抽象后,一切似乎好像开始变得简单了。接下来,我们大致来分析下图中的大致流程:
-
浏览器发起
Http
请求至中间件
,这个中间件
可以概括很多东西。比如:反向代理,路由跳转、网络分发等。 -
Http
请求到达后端服务器Tomcat
,其实你可以将Tomcat
理解为一个接待请求Http
请求的接待员,其主要负责统筹处理请求,而不是所有的事情都自己做。进一步,其会将处理逻辑委托给进程
。
为了进一步方便理解,我们举一个网购
的例子来分析Http
请求处理的大致逻辑。首先,当我们在浏览器键入发起购物的Http
请求后,这个Http
请求在不丢包的前提下,最终会历经万水千山到达Tomcat
;然后,Tomcat
会将Http
请求分发给专门管理订单的进程,并记录其对于某个商品的购买数量。此外,还需要告诉管理库存的进程,进行库存的扣减,同时,还要告诉支付的进程,应该支付的金额。最后,在将处理结果返回给浏览器进行解析。
可以看到Tomcat
会将Http
请求分发给专门的进程
来处理Http
请求,而具体到Java
后端搭建的服务来看,其会将Http
请求交给交给SpringMVC
来进行处理。
其实讲了这么多,重点在于分析清楚Http
请求是如何到达后端服务的;在此基础上,我们重点关注后端服务SpringMVC
内部是如何来完成对Http
请求的处理。
3SpringMVC
内部对于Http
请求的处理
在开始分析之前,我们先来看看后端
服务是如何来定义前端待访问的url
的:
@RestController
@RequestMapping("/demo")
public class HelloWorldController {
@GetMapping(value="/say-hello")
public String sayHello(){
return "helloworld";
}
}
观察上述代码你会发现,其主要做了如下工作:
-
定义了一个待访问的
url
信息,即暴露给前端的地址信息为/demo/say-hello
; -
该
url
的请求方式为Get
方式; -
url
对应的处理逻辑为sayHello
方法。
明白了这些后,不妨思考一个问题,如果你来处理前端传来的/demo/say-hello
请求,你该如何处理? 我想你的思路大致如下:
-
获取请求的
url
信息; -
根据
url
信息获取对应的处理逻辑,具体到SpringMVC
中即寻找url
对应的方法信息; -
处理请求中的参数信息,执行
url
中对应的方法,并将结果返回。
进一步,将上述逻辑翻译为代码
即有如下内容:
public class HttpRequestHandler {
Map<RequestUrl, Method> mapper = new HashMap<>();
* 处理前端传来的请求信息
*/
public Object handle(HttpRequest httpRequest {
// 获取前端传来的url信息
RequestUrl requestKey = getRequestUrl(httpRequest);
Method method = this.mapper.getValue(requesUrl);
Object[] args = resolveArgsAccordingToMethod(httpRequest, method);
return method.invoke(controllerObject, args);
}
事实上,这也就是SpringMVC
中处理Http
请求的底层了逻辑。虽然上述代码中省略了很多方法的编写,但这并不影响理解。
熟悉了SpringMVC
处理Http
请求的底层逻辑后,我们再来进一步看看SpringMVC
在处理Http
请求时的具体细节
。
4Http
请求从Tomcat
转交到SpringMVC
控制器
我们首先来看Tomcat
内部是如何将对Http
请求的处理权
转交给SpringMVC
的。
众所周知,对于 SpringBoot
应用而言,其本身并不提供通信层的支持,它是依赖于 Tomcat、Jetty
等容器来完成通信层的支持。而在SpringBoot
应用中Tomcat
是如何被启动的我们在此便不再赘述了,具体可参考: 一文讲透SpringBoot应用在内嵌Tomcat容器与外置容器下的启动原理[3]。
进一步,当Tomcat
启动后,此时遇到HTTP
请求访问,其会触发 Tomcat
底层提供的 NIO
通信来完成数据的接收,并最终将请求事件丢入线程池去处理,进而根据servlet-mapping
配置的url-pater
来选取对应的Servlet
来进行处理。
这就是为什么使用SpringMVC
时,需要将DispatcherServlet
中的url-pattern
改为/*
拦截所有请求的原因。 而有关DispatcherServlet
的相关介绍可参考:SpringMVC流程分析(二):揭开DispatcherServlet的神秘面纱[4]。
5SpringMVC
控制器对于请求的处理
进一步,当url
请求到达DispatcherServlet
后,最终会调用到其中的doDispatch
方法。而这个方法我们之前曾具体分析过,其大致处理逻辑如下:
这个过程具体会涉及到哪些处理逻辑,在此我们在此就不再赘述了。但其总结来看无非完成如下逻辑:
-
分发,即根据请求寻找对应的执行方法。 这个过程主要涉及到
url
与处理器
之间的匹配。这一过程中会涉及到的组件有:HandlerMapping(处理器映射器)、HandlerAdapter(处理器适配器)
等。 -
执行,反射执行寻找到的执行方法。 这一过程的本质就是通过
反射
机制来调用被@ReqeustMapping
标注的方法
。
进一步,在SpringMVC流程分析(六):为处理器进行合适的“适配”[5]我们曾提到过:“对于前端传来的url
信息,最终会交由RequestMappingHandlerAdapter
中的handleInternal
来进行处理”。更进一步,其本质会将方法反射
执行的逻辑委托给ServletInvocableHandlerMethod
来处理,其相关调用链如下:
(注:SpringMVC
内部这块通过反射
执行方法的逻辑相对来说还是比较复杂的,由于笔者自身水平有限,所以在之前分析中一笔带过,感兴趣的读者可私下自行其内部执行逻辑,笔者期待你的佳作
~~~)
6总结
最后,我们来回顾下本文的内容。首先,我们从Http
请求的处理过程入手,详细讨论了Http
请求处理的全过程。以此为基础,我们重点分析了在后端服务中Http
请求在SpringMVC
中处理的全过程。总结来看SpringMVC
在处理前端传来的url
请求时,主要完成了两项任务:
-
请求分发,即根据
url
寻找对应的处理器; -
方法执行,即执行与对应
url
绑定的方法信息,并将执行结果返回到客户端。
进一步,SpringMVC
内部对于Http
请求的处理过程如下所示:
这张图是笔者从网上找的,上述的ViewResolver、View
其实现在已经很少用了。更多的还是在处处理器
上使用@RestController
或者@ResponseBody
注解,以指示返回的数据应该以JSON
格式响应给客户端。而完成这一工作的组件即为我们之前介绍的HttpMessageConverter
,有关HttpMessageConverter
相关介绍可参考:SpringMVC流程分析(七):HttpMessageConverter——SpringMVC中的消息转换[6]。
其实细心的读者已经注意到了,笔者在介绍SpringMVC
内部对于Http
请求的处理逻辑时,并没有其他文章
一样以上述这样为基础来向写流水账
一样的告诉读者SpringMVC
内部处理Http
请求会经历那些过程,然后使用那些组件。
这样的介绍在笔者看来其只是告诉了一个零散的知识点,即SpringMVC
内部对于Http
请求的处理。而这与你之前学到的知识
是割裂的,其相互无法产生直接的联系。这导致最直接的后果就是你一直在不断的学习,看似学了很多但其实很多时候其实都在做无用功。
所以笔者更希望在介绍知识时能将当前知识点与之前所需知识串联起来,进而将零碎的知识点串联起来,这样才能授人以鱼的同时授人以渔,进而当日后遇到相似问题时也才能举一反三。
如果觉得笔者文章对你很有启发的话,不妨点赞、收藏、关注三连走起,以免错过日后的每一次更新。
参考资料
[1]
https://juejin.cn/column/7256779530392256567: https://juejin.cn/column/7256779530392256567
[2]
https://juejin.cn/column/7256779530392256567: https://juejin.cn/column/7256779530392256567
[3]
https://juejin.cn/post/7283328111504293942: https://juejin.cn/post/7283328111504293942
[4]
https://juejin.cn/post/7258165891279568951: https://juejin.cn/post/7258165891279568951
[5]
https://juejin.cn/post/7263840667825782821: https://juejin.cn/post/7263840667825782821
[6]
https://juejin.cn/post/7265218003863420982: https://juejin.cn/post/7265218003863420982