SpringMVC流程分析(二):揭开DispatcherServlet的神秘面纱
本系列文章皆在分析SpringMVC的核心组件和工作原理,让你从springmvc浩如烟海的代码中跳出来,以一种全局的视角来重新审视SpringMVC的工作原理SpringMVC.
思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。 作者:毅航😜
前言
在使用SpringMVC开发Web应用时,除了会在web.xml中配置之前讨论的ContextLoaderListener项以外(相关内容可参考:SpringMVC流程分析(一):从一行配置入手,搞懂web环境下Ioc容器的构建),还需要在配置一个Servlet,而这个Servlet正是本文所讨论的重点——DispatcherServlet.
下图展示了本系列文章重点分析的组件信息,其中 DispatcherServlet是本文分析的重点。

Servlet的前世今生
随着Web开发工具的不断迭代, Servlet可能已经成为一个相对陌生的话题。接下来,将以餐厅点菜为例,带你快速熟悉Servlet.
当你走进一家餐厅,拿起菜单准备点菜时,总会有一个“服务员”会在你身旁,负责接收你的点菜信息,然后将你的餐单交给后厨去处理,等后厨烹调完毕后,“服务员”会将菜肴端到你的桌前。
类似的,当你在浏览器上访问一个网页或提交表单时,Servlet就像是一个厨师和服务员的组合。它接收你的订单(请求),然后根据你的选择(数据),然后告诉厨师要做什么(执行业务逻辑),最后将做好的食物端到你的桌子上(返回动态生成的页面),让你享受美食.
简而言之,Servlet的作用在于接收并处理客户端的HTTP请求,并生成响应结果.
Servlet的体系结构

Servlet:Servlet体系根接口GenericServlet:Servlet抽象实现类HttpServlet: 对HTTP协议封装的Servlet实现类
Servlet作为一个接口,并不能通过new关键字来进行实例化,所以如果我们期待享受Servlet所提供的服务,则必须依赖于Servlet的实现类HttpServlet,其扩展了GenericServlet类,专门用于处理HTTP请求.
此外,HttpServlet提供了对HTTP请求的封装和处理方法,使得Servlet可以根据Http的请求方法的不同信息,转发至对应的处理方法,如Get请求转发至doGet()处理,Post请求转发至doPost()处理.
Servlet的生命周期
事实上,在Servlet对象的创建、初始化、接收请求、销毁等过程中,都会执行对应的方法,其分别为init()、service()、destory()方法. 而这些方法也被称为是Servlet的生命周期方法中. 这些方法的具体功能如下:
init():在Servlet对象被创建后,容器会调用init()方法进行初始化。该方法只会在Servlet的整个生命周期中被调用一次。可以在init()方法中进行一些初始化操作,例如加载配置文件、建立数据库连接等。service():每当有HTTP请求到达Servlet时,容器会调用service()方法来处理请求,并据请求的类型(GET、POST等)来调用相应的doGet()、doPost()等方法。在这些方法中,可以定义请求处理逻辑、业务操作逻辑等,并生成相应的HTTP响应.destroy():在Servlet容器决定销毁Servlet对象时,会调用destroy()方法。这通常发生在Web应用程序关闭或Servlet容器关闭的时候。在destroy()方法中,你可以进行一些资源释放和清理的操作,例如关闭数据库连接、释放资源等.
总的来看,当Servlet对象创建时,会调用init()进行初始化;在接收HTTP请求时,则会调用service()方法处理请求;而在销毁时会调用destroy()进行资源清理.
Servlet中的请求处理

- 首先,浏览器和服务器建立连接,生成请求数据包,将请求数据包发送给服务器;
- 接着,服务器解析请求数据包,创建
request和response对象,将请求数据存入request对象中; - 随后,服务器调用
Servlet的service()方法,会将request和response作为参数传进来,并将处理结果写入到response中; - 最后,浏览器解析
response中的响应内容,在页面上生成响应内容.
揭秘DispatcherServlet
在Spring MVC中,DispatcherServlet作为一个前端控制器,其负责接收所有的HTTP请求,同时,将请求分发给相应的处理器(Controller)来处理,并最终返回响应结果。
DispatcherServlet体系结构

通过上图不难看出,DispatcherServlet的体系结构可以分为两条脉络,一条以Servlet体系结构为基础,另一条则以Spring为基础,其中HttpServletBean则是两者的一个媒介,进而使得Spring中的某个Bean具有Servlet的相关功能.
但我们并不打算详细分析DispatcherServlet的体系结构,因为这样容易让我们陷入到具体细节中,不利于全局的视野建立. 所以,我们将精力集中在DispatcherServlet之上.
在接下来的分析中,请忘记掉上述DispatcherServlet复杂的继承关系结构,只需要记住:DispatcherServlet是一个Servlet. 所以,我们关注的内容聚焦在Servlet的初始化以及Http请求的处理上.
DispatcherServlet初始化的秘密
因为DispatcherServlet是一个Servlet,所以在DispatcherServlet对象被创建后,Tomcat容器会调用其中的init()方法来完成Servlet的初始化工作,所以若要分析DispatcherServlet初始化的秘密,就应该研究其DispatcherServlet中init()的方法.

DispatcherServlet中的init()方法的逻辑如上图所示,不难发现其中核心方法为initWebApplicationContext,即初始化一个web容器. 其相关代码如下:
(ps:initWebApplicationContext() 的相关逻辑定义在DispatcherServlet的父类FrameWorkServlet之中.)
FrameWorkServlet#initWebApplicationContext()
public abstract class FrameWorkServlet {
// 成员变量 webApplicationContext 用于保存web容器
private WebApplicationContext webApplicationContext;
// ....省略其他无关方法
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
//获取当前环境中的web容器信息,如果Spring和SpringMVC整合,则不需要重新创建
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
//设置父子容器将Spring容器设为SpringMVC的父容器
cwac.setParent(rootContext);
}
// 容器刷新,将SpringMVC配置文件中的属性加载到容器中
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// ....省略其他无关代码
if (wac == null) {
// 如果当前上下文中没有web容器,则重新创建一个
// 主要针对单独使用SpringMVC时的情况
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 调用onRefresh方法,从而为容器中初始化一些组件信息
// DispathcerServlet的核心组件都通过此处完成初始化
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
return wac;
}
// ....省略其他无关方法
}
通过FrameworkServlet的继承体系我们可以看到,其实现了ApplicationContextAware接口,这使得其具有获取到Spring中应用上下文(ApplicationContextContext)的能力. 而FrameworkServlet的成员变量webApplicationContext 可以将 Servlet 上下文与 Spring 容器上下文进行关联,其实际类型 ConfigurableWebApplicationContext.
事实上,如果在web.xml 中配置的 ContextLoaderListener 监听器初始化的容器上下文容器信息. 那么Spring 和 SpringMVC的容器之间便会存在一种父子关系,即 Spring 的容器是父容器,SpringMVC的容器为子容器.
通过上述分析不难看出DispatcherServlet在初始化阶段做的最核心工作就是构建容器,然后加载DispatcherServlet的相关配置信息.
DispatcherServlet请求处理的秘密
在Servlet的请求处理中,每当有Http请求到达Servlet时,都会调用其内部的service()方法来进行处理,并返回相应的处理结果. 接下来,我们将分析DispatcherServlet的service()相关内容,从而分析清楚DispatcherServlet内部是如何来完成一个请求处理的.
在开始分析之前,我们应该明白当Http到达DispatcherServlet时,其首先会调用DispatcherServlet中的service()方法,但该方法的具体实现是在FrameworkServlet中,而FrameworkServlet的service方法则会通过super.service()继续向上调用父类HttpServletBean中service()的方法,但HttpServletBean中并未重写service()方法,所以只能继续向上找到其父类HttpServlet中的service()方法,而此时HttpServlet中的service()方法,逻辑如下:
HttpServlet#service()
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 获取请求方法
String method = req.getMethod();
// 当请求方式为Get时,调用doGet处理请求
if (method.equals(METHOD_GET)) {
doGet(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
}
// ....省略其他无关代码
}
不难发现HttpServlet中service()的主要逻辑为:首先,获取到请求方法的类型,然后,调用doXXX()方法(例如doGet、doPost等)来处理请求,而这些方法的处理逻辑在FrameworkServlet中进行了重新定义,具体如下:
protected final void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 交给DispatcherServlet进行实现
processRequest(request, response);
}
protected final void processRequest(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// ....省略其他无关代码
// doService逻辑交给子类DispatcherServlet实现
doService(request, response);
// ....省略其他无关代码
}
因此,最终的所有处理逻辑都DispatcherServlet中的doService进行处理. 此时,请求处理的整个调用过程如下图所示. 所以如果要分析Http请求在DispatcherServlet的处理过程,只需要关注方法doService即可.
Service()的具体调用关系如下所示.

通过上图可以知道,当一个Http请求到达DispatcherServlet后,其会通过内部的service()方法来完成请求的处理,并最终委托于DispatcherServlet的doService方法来进行处理.
(Ps:由于DispatcherServlet复杂的继承关系,所以service()的调用过程稍显复杂; 但通过梳理service()的调用链我们可以知道,所有Http请求的处理都会委托于DispatcherServlet中的doService.)
在doService中,所有的处理逻辑又会委托于 doDispatch进行处理,其核心代码如下:
protected void doDispatch(HttpServletRequest request,
HttpServletResponse response) throws Exception {
// 省略无关代码....
// 检测请求是否为上传请求,如果是则通过 multipartResolver 将其封装成 MultipartHttpServletRequest对象
processedRequest = checkMultipart(request);
// 获得请求对应的 HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
// 如果获取不到,则根据配置抛出异常或返回 404 错误
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 获得当前 handler 对应的 HandlerAdapter 对象
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 拦截器前置处理逻辑
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 调用 handler 方法,也就是执行对应的方法,并返回视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 后置处理 拦截器
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 处理正常和异常的请求调用结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
// 省略其他无关代码....
}
DispatcherServlet中的doDispatch方法是Spring MVC的核心方法,用于处理HTTP请求并进行请求的分发。它是根据请求的URL和请求类型(GET、POST等)来确定要调用哪个控制器(Controller)的方法,并处理控制器方法的返回结果,最终生成HTTP响应,其大致处理逻辑如下图所示:

- 解析请求信息: 首先,
doDispatch方法会解析HTTP请求的URL、请求类型、请求参数等信息,以确定要调用哪个控制器的哪个方法来处理请求。 - 查找处理器(Handler): 接下来,
doDispatch会根据请求的URL查找合适的处理器(通常是Controller对象)和处理器方法。这个过程是通过Spring的HandlerMapping组件来实现的,HandlerMapping会根据URL和配置的映射规则,找到对应的Controller和方法。 - 执行处理器方法: 一旦找到处理器和方法,
doDispatch会调用该方法,并将HTTP请求的参数传递给方法。Controller方法会执行业务逻辑,并返回一个包含响应数据的ModelAndView对象。 - 处理返回结果: 接着,
doDispatch会根据Controller方法返回的ModelAndView对象,来决定如何处理响应结果。如果返回结果是一个视图(View),doDispatch会通过ViewResolver将逻辑视图名称解析为真实的视图对象,然后使用视图对象来渲染生成HTML等内容。 - 发送响应: 最后,
doDispatch将生成的响应内容发送给客户端(通常是浏览器),完成HTTP响应的过程。
DispatcherServlet中的doDispatch方法可以根据HTTP请求的信息,找到合适的控制器并调用处理器方法,然后处理返回结果,最终生成HTTP响应。所以一定程度上,可以将doDispatch认为是Spring MVC的核心,因此后续我们的讨论都将围绕上图doDispatch的处理逻辑展开.
总结
本文首先介绍了Servlet的相关内容,并以此为基础分析了Spring MVC中的核心组件DispatcherServlet,在整个过程中,我们所秉持的理念一直都是忽视掉DispatcherServlet复杂的继承关系,而将其看做是一个Servlet,进而从Servlet的角度,并对DispatcherServlet的初始化和请求处理进行了详细的分析.
不过在整个处理过程中涉及到SpringMVC中处理请求的组件还没有进行分析,或许你对于许多细节存在疑惑,但不要慌,后续文章将对 SpringMVC的其他核心组件进行分析. 而DispatcherServlet 中的doDispatch方法中的处理逻辑将贯彻整个分析过程,这样有利于加深我们对 SpringMVC 的理解,同时能将DispatcherServlet 组件同其他组件串联起来,进而更好的理解SpringMVC中对于一个请求的处理.
转载自:https://juejin.cn/post/7258165891279568951