【SpringMVC笔记】 - 11 - SpringMVC 执行流程

SpringMVC 笔记 - 11 - SpringMVC 执行流程

学习思路:先看总源码 → 按执行顺序逐段拆解 → 每步贴源码 + 解析 + 作用,对照阅读最容易理解


一、总入口:DispatcherServlet 核心总源码

java 复制代码
public class DispatcherServlet extends FrameworkServlet {

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 1. 获取处理器执行链
        HandlerExecutionChain mappedHandler = getHandler(processedRequest);

        // 2. 获取处理器适配器
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        // 3. 执行所有拦截器 preHandle
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }

        // 4. 执行控制器方法,返回 ModelAndView
        ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        // 5. 执行所有拦截器 postHandle
        mappedHandler.applyPostHandle(processedRequest, response, mv);

        // 6. 处理结果:视图渲染 + 执行 afterCompletion
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
}

二、服务器启动时做了什么?(前置必备)

1. 继承关系

复制代码
DispatcherServlet 
→ FrameworkServlet 
→ HttpServletBean 
→ HttpServlet 
→ GenericServlet 
→ Servlet

DispatcherServlet 本质就是一个 Servlet,由 Web 服务器加载管理。

2. 启动初始化流程(源码链路)

java 复制代码
init() → initServletBean() → initWebApplicationContext() → onRefresh() → initStrategies()

服务器启动阶段,DispatcherServlet的构造函数被执行,

并且调用init()函数,进行初始化

DispatcherServlet类中并没有init()方法

在HttpServlet类中,

java 复制代码
public void init(ServletConfig config) throws ServletException {
    super.init(config);
    this.legacyHeadHandling = Boolean.parseBoolean(config.getInitParameter("jakarta.servlet.http.legacyDoHead"));
}

调用了父类GenericServlet的init()方法

在GenericServlet类中,

java 复制代码
public void init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
}
public void init() throws ServletException {
}

init()方法交由子类进行重写。

也就是子类HttpServletBean中进行重写。

在HttpServletBean类中,

java 复制代码
public final void init() throws ServletException {
    this.initServletBean();
}
protected void initServletBean() throws ServletException {
}

发现init()方法中调用了initServletBean()方法,

initServletBean()由子类FrameworkServlet进行重写。

在FrameworkServlet类中,

java 复制代码
protected final void initServletBean() throws ServletException {
    this.webApplicationContext = this.initWebApplicationContext();
}
protected WebApplicationContext initWebApplicationContext() {
    this.onRefresh(wac);
}
protected void onRefresh(ApplicationContext context) {
}

在initWebApplicationContext()方法中,调用了onRefresh()方法

onRefresh()方法由子类DispatcherServlet进行重写。

在DispatcherServlet类中,

java 复制代码
protected void onRefresh(ApplicationContext context) {
    this.initStrategies(context);
}

最终执行this.initStrategies(context);

3. initStrategies 初始化九大组件

java 复制代码
protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);       // 文件上传解析器
    initLocaleResolver(context);          // 国际化解析器
    initThemeResolver(context);           // 主题解析器
    initHandlerMappings(context);         // 处理器映射器
    initHandlerAdapters(context);         // 处理器适配器
    initHandlerExceptionResolvers(context); // 异常解析器
    initRequestToViewNameTranslator(context); // 视图名转换器
    initViewResolvers(context);           // 视图解析器
    initFlashMapManager(context);         // 重定向数据管理器
}

一句话总结

服务器启动 → 初始化 IOC 容器 → 初始化所有 HandlerMapping、HandlerAdapter、ViewResolver → 建立 URL 与 Controller 方法映射。


三、逐行源码精讲(请求执行全过程)

第 1 步:获取处理器执行链 HandlerExecutionChain

对应源码

java 复制代码
// doDispatch 第一行
HandlerExecutionChain mappedHandler = getHandler(processedRequest);

getHandler 源码

java 复制代码
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        // 遍历所有处理器映射器
        // 通过合适的HandlerMapping才能获取到HandlerExecutionChain对象。
        // 如果处理器方法使用了@RequestMapping注解,那么以下代码中的mapping是:RequestMappingMapping对象
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

解析

  1. 遍历所有 HandlerMapping集合(启动时已创建)
  2. 根据 请求 URL 查找对应的 HandlerMethod
  3. 封装成 HandlerExecutionChain(处理器执行链)

我们处理请求的第一步代码是:

java 复制代码
HandlerExecutionChain mapperHandler = getHandler(processedRequest);

其本质上是调用了:

java 复制代码
HandlerExecutionChain handler = mapping.getHandler(request)

HandlerMapping 是 SpringMVC 的处理器映射器顶层接口 ,是请求映射的核心规范,唯一职责根据请求路径(URL)找到对应的处理器方法(HandlerMethod)

HandlerMapping 提供多种实现,适配不同的映射方式:

  1. RequestMappingHandlerMapping(最常用)
    • 专门处理 @RequestMapping / @GetMapping / @PostMapping 等注解
    • 项目中 99% 场景使用此实现
  2. SimpleUrlHandlerMapping
    • 基于 XML 配置或手动配置 URL 与处理器映射
    • 适用于老式配置、静态资源映射等场景
  3. BeanNameUrlHandlerMapping
    • 根据 Bean 的名称作为 URL 进行映射

总结:用注解就用 RequestMappingHandlerMapping;用 XML 配置就用其他实现类。

HandlerMapping 的创建时机

  • 所有 HandlerMapping 对象,都在 Web 服务器启动阶段创建

  • 创建后统一存入 DispatcherServlet 的集合中:

    java 复制代码
    List<HandlerMapping> handlerMappings;
  • 服务器启动时,SpringMVC 会扫描所有 HandlerMapping 实现并自动注册

  • 同时完成 URL 与 HandlerMethod 的映射关系初始化

这一步执行过程中,直接关联的核心类如下:

  1. HandlerMapping
    • 顶层接口,定义映射规范
  2. RequestMappingHandlerMapping
    • 最常用实现类,处理 @RequestMapping 注解
  3. HandlerExecutionChain
    • 封装本次请求的处理器 + 拦截器
  4. HandlerMethod
    • 封装目标 Controller 方法(beanName + Method 反射对象)
  5. HandlerInterceptor
    • 拦截器接口,被存入执行链的 interceptorList 中

HandlerExecutionChain 结构

java 复制代码
public class HandlerExecutionChain { // HandlerExecutionChain: 处理器执行链对象
    // 真正要执行的 Controller 方法
    Object handler = new HandlerMethod(......) // 实际是 HandlerMethod
    
    // 本次请求所有拦截器
    private List<HandlerInterceptor> interceptorList;
}

作用

找到 "这次请求该执行哪个方法 + 经过哪些拦截器"

HandlerMethod 是什么?

HandlerMethod是最核心的要执行的目标,处理器方法

HandlerMethod是在web服务器启动时初始化spring容器的时候,就创建好了。

这个类当中比较重要的属性包括:beanName和Method

例如,以下代码:

java 复制代码
@Controller("userController")
public class UserController{
    @RequestMapping("/login")
    public String login(User user){
        return ....;
    }
}

那么以上代码对应了一个HandlerMethod对象:

java 复制代码
public class HandlerMethod{
    private String beanName = "userController";
    private Method method = UserController.class.getMethod("login", User.class);
}

第 2 步:获取处理器适配器 HandlerAdapter

对应源码

java 复制代码
// doDispatch 第二行
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

getHandlerAdapter 源码

java 复制代码
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            // 判断适配器是否支持当前处理器
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler...");
}

解析

  1. 遍历所有 HandlerAdapter
  2. 调用 supports(handler) 匹配
  3. 返回对应适配器(最常用:RequestMappingHandlerAdapter

为什么必须用适配器?

复制代码
// 当前执行代码
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

1. 基础定位

  • HandlerAdapter 是处理器适配器顶层接口 ,底层采用适配器模式
  • 每一种处理器(Controller/HandlerMethod)都有与之匹配的适配器。
  • 核心职责:参数处理、数据绑定、类型转换、方法调用、返回值封装。

2. 常用实现类

  • RequestMappingHandlerAdapter(最常用)
  • 专门适配标注 @RequestMapping / @GetMapping / @PostMapping的处理器方法。
  • 其他适配器:适配老式 Controller 接口、HttpRequestHandler 等。

3. 获取 Handler 类型

  • mappedHandler.getHandler() 获取到的是 HandlerMethod 对象
  • HandlerMethod 封装了目标 Controller 的 beanName 与 Method 反射信息。

4. 创建与存储时机

  • 所有 HandlerAdapter 实现类服务器启动阶段就已创建。

  • 统一存入 DispatcherServlet 集合:

    java 复制代码
    List<HandlerAdapter> handlerAdapters;

5. 引入 HandlerAdapter 的核心原因

为了遵循开闭原则 + 彻底解耦

DispatcherServlet 只负责调度,不关心 Handler 是什么、怎么执行。


6. 使用 HandlerAdapter 的三大核心理由

① 统一接口,适配多种 Handler 类型(适配器模式)

SpringMVC 支持多种处理器:

  • HandlerMethod:@Controller + @RequestMapping 注解方式(主流)

  • Controller 接口:传统实现 Controller 接口的控制器

  • HttpRequestHandler:处理静态资源、底层 HTTP 请求

  • 有的 Handler 是 方法(HandlerMethod)

  • 有的 Handler 是 (Controller 接口)

  • 有的 Handler 是 Servlet

  • 有的 Handler 直接操作 response

  • 有的 Handler 返回 ModelAndView

  • 有的 Handler 返回 void

调用方式完全不一样!

如果不用适配器,DispatcherServlet 会出现大量 instanceof 判断:

java 复制代码
// 不使用适配器的丑陋代码(违反开闭原则)
Object handler = mappedHandler.getHandler();
if (handler instanceof HandlerMethod) {
    // 解析参数、反射调用...
} else if (handler instanceof Controller) {
    // 强制转换、调用 handleRequest...
} else if (handler instanceof HttpRequestHandler) {
    // 调用 handleRequest...
}

新增处理器必须改 DispatcherServlet 源码,严重违反开闭原则

使用适配器后代码极简:

java 复制代码
HandlerAdapter ha = getHandlerAdapter(handler);
ModelAndView mv = ha.handle(request, response, handler);

DispatcherServlet 完全不关心 Handler 类型,只调用适配器。


② 封装 "脏活累活":复杂逻辑全部交给适配器

HandlerMethod 只是方法元数据(通过反射获取的 Method 对象等元数据),自己无法执行

适配器(尤其 RequestMappingHandlerAdapter)负责:

  • 参数解析:从 Request 中提取 @RequestParam、@RequestBody 等参数
  • 数据绑定:WebDataBinder 完成字符串→类型转换
  • 消息转换:HttpMessageConverter 实现 JSON ↔ POJO
  • 参数校验:处理 @Valid 校验逻辑
  • 反射调用:执行 Controller 目标方法
  • 返回值处理:将 String/ResponseEntity/void 统一处理

适配器让 DispatcherServlet 保持极简、专注调度。

HandlerMethod 只是一个方法的描述(通过反射获取的 Method 对象等元数据)。

它自己是不会执行的,而且直接反射调用它非常复杂。

对于一个 Controller 方法:

java 复制代码
@RequestMapping("/hello")
public String sayHello(@RequestParam String name, @RequestBody User user, HttpServletRequest request) {
    return "success";
}

DispatcherServlet 拿到这个 HandlerMethod 后,面临巨大的挑战:

  • 参数解析:方法需要 name,需要 user 对象,需要 request。
  • 这些数据从哪里来?
  • 如何从 HTTP 请求中解析出来?
  • 如何把 JSON 转成 User 对象?
  • 数据绑定:如何把 String 类型的 "18" 绑定到 Integer 类型的参数上?
  • 验证:如果有 @Valid,如何执行校验?
  • 返回值处理:方法返回 "success" 字符串,是指跳转页面,还是直接响应文本,还是 JSON?

​ 这就是 HandlerAdapter(特别是 RequestMappingHandlerAdapter)存在的意义:

​ 它内部包含了大量的 ArgumentResolver(参数解析器) 和 ReturnValueHandler(返回值处理器)。它负责:

  • 分析方法签名。
  • 把 HTTP 请求的数据"适配"成方法需要的参数。
  • 反射调用你的 Controller 方法。
  • 把方法的返回值"适配"成 DispatcherServlet 能看懂的 ModelAndView(或者直接处理完响应)。

③ 统一返回结构,标准化流程

不同 Handler 返回值五花八门:

  • 返回 String(视图名)
  • 返回 ResponseEntity
  • 返回 void
  • 直接操作输出流

HandlerAdapter 的 handle() 方法约定:

统一返回 ModelAndView(或 null)

让 DispatcherServlet 后续渲染流程标准化、稳定、可预期

总结

HandlerAdapter 是处理器的 "万能翻译官"

把不同类型的 Handler、复杂的参数绑定、多样的返回值,全部适配成 DispatcherServlet 能统一处理的标准格式 ,同时保证开闭原则与高度扩展性


第 3 步:执行拦截器 preHandle

对应源码

java 复制代码
// doDispatch 第三段
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}

applyPreHandle 源码

java 复制代码
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    for (int i = 0; i < this.interceptorList.size(); i++) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        if (!interceptor.preHandle(request, response, this.handler)) {
            // 只要一个返回 false,直接触发 afterCompletion 并中断请求
            triggerAfterCompletion(request, response, null);
            return false;
        }
        this.interceptorIndex = i;
    }
    return true;
}

解析

  • 正序执行 所有拦截器 preHandle()
  • 任何一个返回 false,直接终止整个流程
  • 常用于:登录校验、权限拦截、日志、请求限流
  • 遍历List集合中,从List集合中取出每一个 HandlerInterceptor对象,调用 preHandle,i++,可见是顺序调用。

第 4 步:执行 Controller 方法(核心业务)

对应源码

java 复制代码
// doDispatch 第四段
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

mv 是ModelAndView对象

ha 是处理器适配器,HandlerAdapter

如果是@RequestMapping注解对应的,那么就是 RequestMappingHandlerAdapter

这个方法是最核心的,调用请求路径对应的HandlerMethod。(调用处理器方法)

内部核心流程

java 复制代码
// RequestMappingHandlerAdapter 内部
protected ModelAndView handleInternal(HttpServletRequest request,
                                          HttpServletResponse response,
                                          HandlerMethod handlerMethod) throws Exception {
    ModelAndView mav = this.invokeHandlerMethod(request, response, handlerMethod);
    retuern mav;
}

protected ModelAndView invokeHandlerMethod(...) {
    // 1. 创建数据绑定工厂 WebDataBinder
    WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    
    // 2. 创建可执行方法对象
    ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    
    // 3. 参数解析 → 数据绑定 → 反射调用方法
    // 3.1 给可调用的方法绑定数据
    invocableMethod.setDataBinderFactory(binderFactory);
    
    // 3.2 给可调用的方法设置参数
    invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
    
    // 3.3 可调用的方法执行
    invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]); 
    
}

HandlerAdapter 中完成的核心工作

  1. 参数解析:从 request 中提取请求数据
  2. 数据转换:如果是 JSON/XML 等请求体数据, 调用 HttpMessageConverter 将其转换为 POJO 对象。
  3. 数据绑定: 通过 WebDataBinder 将转换后的数据绑定到 HandlerMethod 的方法参数上。
  4. 执行调用:反射执行目标 Controller 方法(HandlerMethod)。
  5. 结果封装:将方法返回值封装为 ModelAndView 对象。

HttpMessageConverter 调用时机

HandlerAdapter#handle() 方法内部,执行流程:

  1. 解析方法参数
  2. 判断参数是否需要消息转换
    • 是否标注 @RequestBody
    • 是否是 JSON/XML 等格式数据
  3. 如果需要 → 调用 HttpMessageConverter
    • 将请求体 JSON/XML → 转为 POJO
  4. 数据绑定完成
  5. 反射调用 Controller 方法
  6. 返回值如果是 @ResponseBody → 再次调用 HttpMessageConverter
    • 将 POJO → JSON/XML 输出

第 5 步:执行拦截器 postHandle

对应源码

java 复制代码
// doDispatch 第五段
mappedHandler.applyPostHandle(processedRequest, response, mv);

applyPostHandle 源码

java 复制代码
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
    // 倒序执行
    for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        interceptor.postHandle(request, response, this.handler, mv);
    }
}

解析

  • 倒序执行 所有拦截器 postHandle()
  • 方法执行完毕、视图渲染之前执行
  • 可修改 ModelAndView 中的数据或视图名
  • 常用于:统一数据包装、日志、敏感信息过滤
  • 通过源码,可以很轻松的看到,从List集合中逆序(i--)逐一取出拦截器对象,并且调用拦截器的 postHandle方法。

第 6 步:处理结果(渲染 + afterCompletion)

对应源码

java 复制代码
// doDispatch 最后一行
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

processDispatchResult 源码

java 复制代码
private void processDispatchResult(...) throws Exception {
    // 1. 视图渲染
    render(mv, request, response);
    
    // 2. 执行拦截器 afterCompletion
    mappedHandler.triggerAfterCompletion(request, response, null);
}

子步骤 6.1:视图渲染 render

对应源码
java 复制代码
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 1. 解析视图名 → 得到 View 对象
    View view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    
    // 2. 执行视图渲染
    view.render(mv.getModelInternal(), request, response);
}
resolveViewName 源码
java 复制代码
protected View resolveViewName(...) throws Exception {
    for (ViewResolver viewResolver : this.viewResolvers) {
        View view = viewResolver.resolveViewName(viewName, locale);
        if (view != null) {
            return view;
        }
    }
    return null;
}
解析
  1. 遍历 ViewResolver(如 InternalResourceViewResolver、ThymeleafViewResolver)
  2. 逻辑视图名 (如 success)解析为 View 对象
  3. 调用 view.render() 渲染页面 / 数据,响应浏览器

子步骤 6.2:执行 afterCompletion

对应源码
java 复制代码
mappedHandler.triggerAfterCompletion(request, response, null);
triggerAfterCompletion 源码
java 复制代码
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) {
    // 倒序执行
    for (int i = this.interceptorIndex; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        try {
            interceptor.afterCompletion(request, response, this.handler, ex);
        } catch (Throwable ex2) {
            logger.error("afterCompletion threw exception", ex2);
        }
    }
}
解析
  • 倒序执行
  • 无论是否异常,一定执行(类似 finally)
  • 常用于:资源释放、耗时统计、日志收尾、清理 ThreadLocal
  • 通过源码可以看出,也是通过逆序(i--)的方式进行拦截器的调用,调用拦截器的afterCompletion方法。

四、完整执行流程

  1. 请求 → DispatcherServlet
  2. HandlerMapping → 获取 HandlerExecutionChain
  3. HandlerAdapter → 获取对应适配器
  4. 正序执行拦截器 preHandle
  5. 执行 Controller 方法 → 返回 ModelAndView
  6. 倒序执行拦截器 postHandle
  7. ViewResolver 解析视图 → View 渲染响应
  8. 倒序执行拦截器 afterCompletion

五、核心对象职责

对象 核心作用
DispatcherServlet 前端控制器,统一调度所有流程
HandlerMapping 根据 URL 找 Controller 方法
HandlerExecutionChain 封装 Handler + 所有拦截器
HandlerAdapter 适配并调用 Controller 方法
HandlerMethod 封装 Controller 方法(反射用)
ModelAndView 封装业务数据 + 逻辑视图名
ViewResolver 视图名 → View 对象
View 渲染页面,响应浏览器
HandlerInterceptor 拦截器:pre/post/after 三段处理
相关推荐
三品吉他手会点灯7 小时前
C语言学习笔记 - 43.运算符与表达式 - 运算符1 - 运算符的分类和简单介绍
c语言·笔记·学习·算法
疯狂打码的少年7 小时前
中断处理过程与中断优先级
笔记
心之伊始7 小时前
Java 后端接入大模型:从 Token、并发到推理成本的完整估算方法
java·spring boot·性能优化·大模型·llm
likerhood7 小时前
WSL 下安装 Miniconda 笔记
笔记·wsl
BlackTurn8 小时前
技术经理投标
java
YG亲测源码屋8 小时前
java配置环境变量、jdk环境变量配置、java环境变量设置方法
java·开发语言
MIUMIUKK8 小时前
从语法层面,看懂 Python 的特殊处
java·开发语言·python
hujinyuan201608 小时前
2026年3月 中国电子学会青少年软件编程(Python)三级考试试卷 真题及答案
java·python·算法
basketball6168 小时前
C++ 高级编程:2. 基本线程池实现
java·开发语言·c++
MageGojo9 小时前
天气 API 接入实战:基于 ApiZero 实现实时天气、分钟级降水和 15 天预报查询
java·后端·spring·api 接口接入·接口实战