springMvc概述——重点讲解核心的执行流程
本文基于SpringMVC核心执行流程进行源码分析,重点讲解了DispatcherServlet的工作机制。文章首先展示了SpringMVC经典执行流程图,随后通过源码详细解析了doDispatch()方法的处理流程,包括获取HandlerExecutionChain、适配器模式调用HandlerAdapter、拦截器链执行等关键步骤。特别强调了HandlerMapping和HandlerAdap
springMvc概述——重点讲解核心的执行流程,从源码分析
需知
本文是在之前详细学完老杜的springMvc课程之后进行复习所写,知识点不会很详细,主要围绕springMvc的核心执行流程讲解,也就是下图,这是重点,在这个流程图中我们也是重点讲解前面10个步骤,因为现在很少能用到springMvc了,都是分离项目,其余的基础知识需要自行了解。
核心内容
先大致熟悉一下,我们慢慢拆分,其实也没多少东西
当然网上更多流行的是下面这张图,都是一样的
java代码(引用自老杜的springmvc笔记
)
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 根据请求对象request获取
// 这个对象是在每次发送请求时都创建一个,是请求级别的
// 该对象中描述了本次请求应该执行的拦截器是哪些,顺序是怎样的,要执行的处理器是哪个
HandlerExecutionChain mappedHandler = getHandler(processedRequest);
// 根据处理器获取处理器适配器。(底层使用了适配器模式)
// HandlerAdapter在web服务器启动的时候就创建好了。(启动时创建多个HandlerAdapter放在List集合中)
// HandlerAdapter有多种类型:
// RequestMappingHandlerAdapter:用于适配使用注解 @RequestMapping 标记的控制器方法
// SimpleControllerHandlerAdapter:用于适配实现了 Controller 接口的控制器
// 注意:此时还没有进行数据绑定(也就是说,表单提交的数据,此时还没有转换为pojo对象。)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 执行请求对应的所有拦截器中的 preHandle 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 通过处理器适配器调用处理器方法
// 在调用处理器方法之前会进行数据绑定,将表单提交的数据绑定到处理器方法上。(底层是通过WebDataBinder完成的)
// 在数据绑定的过程中会使用到消息转换器:HttpMessageConverter
// 结束后返回ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 执行请求对应的所有拦截器中的 postHandle 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 处理分发结果(在这个方法中完成了响应)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
// 根据每一次的请求对象来获取处理器执行链对象
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
// HandlerMapping在服务器启动的时候就创建好了,放到了List集合中。HandlerMapping也有多种类型
// RequestMappingHandlerMapping:将 URL 映射到使用注解 @RequestMapping 标记的控制器方法的处理器。
// SimpleUrlHandlerMapping:将 URL 映射到处理器中指定的 URL 或 URL 模式的处理器。
for (HandlerMapping mapping : this.handlerMappings) {
// 重点:这是一次请求的开始,实际上是通过处理器映射器来获取的处理器执行链对象
// 底层实际上会通过 HandlerMapping 对象获取 HandlerMethod对象,将HandlerMethod 对象传递给 HandlerExecutionChain对象。
// 注意:HandlerMapping对象和HandlerMethod对象都是在服务器启动阶段创建的。
// RequestMappingHandlerMapping对象中有多个HandlerMethod对象。
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// 渲染
render(mv, request, response);
// 渲染完毕后,调用该请求对应的所有拦截器的 afterCompletion方法。
mappedHandler.triggerAfterCompletion(request, response, null);
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 通过视图解析器返回视图对象
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 真正的渲染视图
view.render(mv.getModelInternal(), request, response);
}
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
// 通过视图解析器返回视图对象
View view = viewResolver.resolveViewName(viewName, locale);
}
}
流程拆分
一、springMvc启动前核心步骤
根据流程图来说,不同的步骤都调用了一些方法比如适配器、过滤器等等,那这些对象都是哪里来的呢?我们根据源码找一下。
根据UML类图可以很清晰的看到DispatcherServlet的继承关系,其中在HttpServlet有一个init方法是在程序启动的时候就会执行,我们根据继承关系一步一步寻找。
二、流程步骤拆分(步骤流程图)
上面说过在启动的时候是有一部分逻辑执行的,但是具体执行了哪些,顺序又是怎样,也是我们必须要了解的一点,毕竟这是经典的框架。
Servlet 容器初始化 (Tomcat)
- → Servlet.init(ServletConfig) (容器调用接口方法)
- → GenericServlet.init(ServletConfig) (保存配置,调用无参init模板方法)
- →
GenericServlet
.init() (模板方法) - → HttpServlet.init() (默认空实现)
- → HttpServletBean.init() (Spring介入:注入Init-Params为Bean属性)
- → HttpServletBean.initServletBean() (模板方法)
- → FrameworkServlet.initServletBean() (创建/获取 Spring 上下文)
- → FrameworkServlet.initWebApplicationContext()
- → FrameworkServlet.configureAndRefreshWebApplicationContext()
- → ApplicationContext.refresh() (Spring 容器核心刷新流程)
- → ApplicationContext.onRefresh() (Spring 容器模板方法)
- → DispatcherServlet.onRefresh() (Spring MVC 初始化入口)
- → DispatcherServlet.initStrategies() (初始化 MVC 九大组件)
OK,先不要着急,也不要纠结能不能看懂,这个流程是容器启动的时候最详细的执行流程,其中分为两种初始化容器,一个是Server原生的初始化流程
,一个是spring的初始化流程
,两者配合共同组成了springMvc的启动流程,其中的GenericServlet是Spring介入的起点,后面我们细说。
上面的流程图很清晰的描述了启动整个过程中各类的关系和方法之间的调用,我们先看个大概,配合下面的详细解读再回过来就很清晰了。
三、详细步骤解读(启动前)
第一部分:HttpServlet.init() 的调用 (由 Servlet 容器触发)
1. 起点(Servlet容器启动)
Web 服务器(如 Tomcat)启动时,会根据 web.xml 或 Servlet 注解找到所有需要加载的 Servlet,其中就包括配置的 DispatcherServlet。
2. 容器调用(init方法——ServletConfig)
对于每个 Servlet,容器会调用其生命周期起始方法 init(ServletConfig config)
。这个方法定义在 Servlet 接口中。
GenericServlet 提供了此方法的一个基本实现。
3. 执行路径到达 (HttpServletBean)
- 调用链:Servlet.init(ServletConfig) -> GenericServlet.init(ServletConfig) -> HttpServlet.init() -> HttpServletBean.init()
- 关键: HttpServlet 重写了 init() 方法,但它又调用了 super.init(),最终会调用到 GenericServlet 的 init() 方法。然而,Spring 的 HttpServletBean 重写了 GenericServlet 的 init() 方法,这是
Spring 介入的起点
。
4. HttpServletBean.init() 的关键操作
-
这个方法的主要目的是将 Servlet 的初始化参数()作为 Bean 属性
注入到 DispatcherServlet
中(例如 contextConfigLocation)。 -
在这个方法的最后,它调用了一个模板方法:initServletBean()。
第二部分:onRefresh() 的调用 (由 Spring 容器刷新触发)
5. 执行路径到达 FrameworkServlet.initServletBean()
-
HttpServletBean.initServletBean() 是一个空方法,具体实现由子类 FrameworkServlet 完成。
-
FrameworkServlet 重写了 initServletBean(),它的核心任务是
初始化或获取该 Servlet 所属的 Spring WebApplicationContext
。
6. Spring 容器的创建与刷新
在 FrameworkServlet.initWebApplicationContext() 方法中,它会:
-
查找是否存在根上下文(Root WebApplicationContext)。
-
创建本 Servlet 独有的 WebApplicationContext(通常是一个 XmlWebApplicationContext 或 AnnotationConfigWebApplicationContext)。
-
最关键的一步: 调用 configureAndRefreshWebApplicationContext() 方法,该方法最终会调用 wac.refresh()。这里的 refresh() 是 Spring 容器启动和刷新的核心方法,它会加载 Bean 定义、初始化所有单例 Bean 等。
7. refresh() 触发 onRefresh()
-
Spring 容器的 refresh() 方法会按顺序调用一系列生命周期方法。其中一个步骤是 initApplicationEventMulticaster(),另一个是 onRefresh()。
-
onRefresh() 是 ApplicationContext 中的一个模板方法,专门设计用于让子类在容器刷新时执行特定的初始化操作。
8. 执行路径到达 DispatcherServlet.onRefresh()
-
DispatcherServlet 作为 Spring Web MVC 的核心,它重写了 onRefresh() 方法。
-
在这个方法中,DispatcherServlet 初始化了它运行所需的 9 大核心策略组件:
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context); // 文件上传解析器
initLocaleResolver(context); // 本地化解析器
initThemeResolver(context); // 主题解析器
initHandlerMappings(context); // 处理器映射器(关键!)
initHandlerAdapters(context); // 处理器适配器(关键!)
initHandlerExceptionResolvers(context); // 异常解析器
initRequestToViewNameTranslator(context);
initViewResolvers(context); // 视图解析器(关键!)
initFlashMapManager(context);
}
注意看上面的
initHandlerMappings
和initHandlerAdapters
两个方法,这是我们http请求到达Spring之后需要处理的两个核心,也是容器启动时必要的步骤,不然请求是无法处理的。
四、详细步骤解读(接收请求和处理请求)
1. 用户发起请求 (Request)
-
描述:用户通过浏览器发起一个HTTP请求(例如:http://example.com/app/greeting)。
-
源码入口:请求首先到达 DispatcherServlet,由于其本质是一个 HttpServlet,所以请求会由其 service() 方法开始处理。最终,核心的请求处理会路由到
DispatcherServlet.doDispatch(HttpServletRequest request, HttpServletResponse response)
方法。这是整个流程的核心调度方法。
2. 前端控制器查找处理器 (Handler Mapping)
-
描述:DispatcherServlet 咨询一个或多个 HandlerMapping Bean:“这个请求的URL应该由哪个处理器(Handler)来处理?”。HandlerMapping 根据请求的URL路径进行匹配。
-
如何映射:基于注解(@Controller, @RequestMapping)的方式,由RequestMappingHandlerMapping 来处理。
-
返回结果:HandlerMapping 返回的不是一个简单的Controller对象,而是一个 HandlerExecutionChain 对象。这个对象包含了:
-
目标 Handler:通常是 @Controller 类中具体方法的映射信息。
-
拦截器 (Interceptors):配置好的、需要在该请求前后执行的拦截器列表。
-
3. 前端控制器调用处理器适配器 (Handler Adapter)
-
描述:DispatcherServlet 已经拿到了要执行哪个Handler的信息。但是Handler的种类多种多样(例如:普通的Controller、@RequestMapping注解方法、HttpRequestHandler等),如何统一地调用它们呢?这就是 HandlerAdapter 的职责。它使用了适配器设计模式。
-
适配过程:DispatcherServlet 会遍历所有的 HandlerAdapter,问哪一个“你支持这种类型的Handler吗?”。找到支持的适配器后,就用它来实际执行Handler。
注意了!!!:上面只是获取到了对应的适配器,那么获取适配器之后SpringMvc会做什么呢?这是一个值得思考的问题,虽然我们都知道拿到适配器会调用一些方法,可是具体怎么调用也是非常重要的一个步骤,请看下面。
第一部分:HTTP 缓存协商(Conditional GET / Last-Modified)
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
- 条件判断 (if (isGet || HttpMethod.HEAD.matches(method)))
-
目的:检查当前请求是否是
GET 或 HEAD 方法
。 -
原因:只有这两种方法通常是用于获取资源(如HTML页面、图片、数据),并且是
可缓存的
。浏览器对 POST、PUT、DELETE 等方法的请求通常不会进行缓存协商。
- 获取最后修改时间 (ha.getLastModified(…))
-
目的:DispatcherServlet 询问找到的 HandlerAdapter:“这个Handler(控制器方法)所代表的资源,最后修改时间是什么?”
-
工作原理:HandlerAdapter 会去调用控制器方法(或检查Handler)上可能存在的 @RequestMapping 的
lastModified
属性,或者更常见的,调用一个LastModified 接口的方法
。如果开发者在控制器中实现了相关的逻辑(例如,查询数据库中某条记录的最后更新时间),就可以返回一个时间戳(long类型)。如果该资源没有最后修改时间的概念,则返回 -1。
- 检查是否未修改 (checkNotModified(…))
-
目的:这是 HTTP 条件请求(Conditional Request) 的核心。
-
工作原理:
ServletWebRequest.checkNotModified(long lastModified)
方法会做以下事情:-
读取浏览器请求头中的 If-Modified-Since。
-
将这个时间与传入的 lastModified 参数进行比较。
-
如果资源未修改(即 lastModified <= If-Modified-Since)
- 将响应状态码设置为 304 Not Modified。
- 可能会添加新的 Last-Modified 和 Cache-Control 等头。
- 方法返回 true。
如果资源已被修改:
- 在响应中添加 Last-Modified 头,值为当前资源的最后修改时间。
- 方法返回 false。
- 最终结果 (if (… && isGet) { return; }):
如果检查发现资源未修改(checkNotModified 返回 true)并且是GET请求,则方法直接 return。
这意味着: 后续的业务逻辑计算、数据库查询、视图渲染等昂贵操作全部被跳过!
Spring MVC 直接返回一个轻量的 304 响应
,告诉浏览器:“你本地缓存的内容仍然有效,直接使用它吧。”
这极大地提升了性能,减少了服务器不必要的负载。
第二部分:执行拦截器链(Interceptor Pre-Handle)
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
-
目的:在正式执行控制器方法 之前,执行拦截器链中所有拦截器的 preHandle 方法。
-
工作原理:
mappedHandler 是 HandlerExecutionChain,它包含了一个拦截器列表。
applyPreHandle(…) 方法会按顺序遍历这个列表,调用每一个拦截器的 boolean preHandle(…) 方法。 -
返回值处理:
如果某个拦截器的 preHandle 返回 false:表示该拦截器认为请求不应该继续处理(例如,身份验证失败、权限不足)
。applyPreHandle 会立即返回 false,并且会触发已执行成功的拦截器的 afterCompletion 回调(进行一些清理工作)。
如果所有拦截器的 preHandle 都返回 true:表示请求被放行,可以继续执行控制器方法。applyPreHandle 返回 true。 -
最终结果:
如果 !mappedHandler.applyPreHandle(…) 为 true(即 applyPreHandle 返回了 false),则 doDispatch 方法直接 return,请求处理在此终止。
5. 处理器返回 ModelAndView
-
描述:Controller方法执行完毕后,会返回一个结果。这个结果通常会被HandlerAdapter包装成一个 ModelAndView 对象。
-
什么是 ModelAndView:
-
Model:一个包含业务数据的模型(本质是一个Map),这些数据最终会被放入请求属性中,供视图页面使用,也就是在jsp页面中通过$符号获取的那些数据。
-
View:一个逻辑视图名(如"userList"),指定使用哪个视图来渲染输出。
-
-
返回方式:
-
方法可以直接返回 ModelAndView 对象。
-
更常见的是返回一个String类型的视图名,方法参数中的Model map用于存放数据,适配器会将其组合成ModelAndView。
-
6、前端控制器处理视图解析 (View Resolution)
此模块不予说明,已经没有太大的意义了,前后端分离已经完全替代视图解析器。
7、视图渲染并返回响应 (View Rendering & Response)
此模块不予说明,已经没有太大的意义了,前后端分离已经完全替代视图解析器。
总结
关于SpringMvc,上述的解读我想已经够用了,这毕竟是一个经典的框架,虽然后续的springboot和springCloud已经是我们现在开发的主要框架,但底层还是离不开springMvc,这也是面试中非常常见的问题。好了,这部分就结束了,期待再次见面。
更多推荐
所有评论(0)