在当下这个时代,我们每天都会借助浏览器浏览很多内容。但你是否有考虑过当你在浏览器中访问某一个网址时候,这背后都发生了那些事情呢?
事实上,当在浏览器中键入url后,其背后的处理逻辑可大致如下图所示:
可以看到,键入url后其大致会经历如下几个步骤:
实际上Http请求的处理流程会涉及到很多复杂细节,笔者在此也只是做一个抛砖引玉的介绍,其主要目的也是在于让读者对于Http请求的大致处理过程有一个大致的认识。
虽然上述已经对Http请求的处理流程进行了极大的简化,但可能你还是会觉得上述对于Http请求处理的有些繁琐。那能不能对上述步骤再进行一层抽象,让其更容易理解呢?答案当然是肯定的!
基于此,我们对上图所示的Http请求处理流程进行再一次抽象,得到下图所示内容:
可以看到当对复杂事物进行抽象后,一切似乎好像开始变得简单了。接下来,我们大致来分析下图中的大致流程:
为了进一步方便理解,我们举一个网购的例子来分析Http请求处理的大致逻辑。首先,当我们在浏览器键入发起购物的Http请求后,这个Http请求在不丢包的前提下,最终会历经万水千山到达Tomcat;然后,Tomcat会将Http请求分发给专门管理订单的进程,并记录其对于某个商品的购买数量。此外,还需要告诉管理库存的进程,进行库存的扣减,同时,还要告诉支付的进程,应该支付的金额。最后,在将处理结果返回给浏览器进行解析。
可以看到Tomcat会将Http请求分发给专门的进程来处理Http请求,而具体到Java后端搭建的服务来看,其会将Http请求交给交给SpringMVC来进行处理。
其实讲了这么多,重点在于分析清楚Http请求是如何到达后端服务的;在此基础上,我们重点关注后端服务SpringMVC内部是如何来完成对Http请求的处理。
在开始分析之前,我们先来看看后端服务是如何来定义前端待访问的url的:
@RestController
@RequestMapping("/demo")
public class HelloWorldController {
@GetMapping(value="/say-hello")
public String sayHello(){
return "helloworld";
}
}
观察上述代码你会发现,其主要做了如下工作:
明白了这些后,不妨思考一个问题,如果你来处理前端传来的/demo/say-hello请求,你该如何处理? 我想你的思路大致如下:
进一步,将上述逻辑翻译为代码即有如下内容:
public class HttpRequestHandler {
// 定义url信息和方法间的映射关系
Map mapper = new HashMap<>();
/**
* 处理前端传来的请求信息
*/
public Object handle(HttpRequest httpRequest {
// 获取前端传来的url信息
RequestUrl requestKey = getRequestUrl(httpRequest);
// 根据url信息获取对应的处理逻辑
Method method = this.mapper.getValue(requesUrl);
// 解析前端传递的参数信息
Object[] args = resolveArgsAccordingToMethod(httpRequest, method);
// 执行方法,将结果进行返回
return method.invoke(controllerObject, args);
}
事实上,这也就是SpringMVC中处理Http请求的底层了逻辑。虽然上述代码中省略了很多方法的编写,但这并不影响理解。
熟悉了SpringMVC处理Http请求的底层逻辑后,我们再来进一步看看SpringMVC在处理Http请求时的具体细节。
我们首先来看Tomcat内部是如何将对Http请求的处理权转交给SpringMVC的。
众所周知,对于 SpringBoot应用而言,其本身并不提供通信层的支持,它是依赖于 Tomcat、Jetty 等容器来完成通信层的支持。而在SpringBoot应用中Tomcat是如何被启动的我们在此便不再赘述了。
进一步,当Tomcat启动后,此时遇到HTTP 请求访问,其会触发 Tomcat 底层提供的 NIO 通信来完成数据的接收,并最终将请求事件丢入线程池去处理,进而根据servlet-mapping配置的url-pater来选取对应的Servlet来进行处理。
这就是为什么使用SpringMVC时,需要将DispatcherServlet中的url-pattern改为/*拦截所有请求的原因。
进一步,当url请求到达DispatcherServlet后,最终会调用到其中的doDispatch方法。而这个方法我们之前曾具体分析过,其大致处理逻辑如下:
这个过程具体会涉及到哪些处理逻辑,在此我们在此就不再赘述了。但其总结来看无非完成如下逻辑:
进一步,在SpringMVC中:“对于前端传来的url信息,最终会交由RequestMappingHandlerAdapter中的handleInternal来进行处理”。更进一步,其本质会将方法反射执行的逻辑委托给ServletInvocableHandlerMethod来处理,其相关调用链如下:
(注:SpringMVC内部这块通过反射执行方法的逻辑相对来说还是比较复杂的,由于笔者自身水平有限,所以在之前分析中一笔带过,感兴趣的读者可私下自行其内部执行逻辑,笔者期待你的佳作~~~)
最后,我们来回顾下本文的内容。首先,我们从Http请求的处理过程入手,详细讨论了Http请求处理的全过程。以此为基础,我们重点分析了在后端服务中Http请求在SpringMVC中处理的全过程。总结来看SpringMVC在处理前端传来的url请求时,主要完成了两项任务:
进一步,SpringMVC内部对于Http请求的处理过程如下所示:
这张图是笔者从网上找的,上述的ViewResolver、View其实现在已经很少用了。更多的还是在处处理器上使用@RestController或者@ResponseBody注解,以指示返回的数据应该以JSON格式响应给客户端。