【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 三段处理
相关推荐
道长爱睡懒觉2 小时前
蓝牙,导航,仪表,TBOX,OTA
笔记
笨蛋不要掉眼泪2 小时前
面试篇-java基础上
java·后端·面试·职场和发展
itzixiao2 小时前
L1-054 福到了(15 分)[java][python]
java·python·算法
Flittly2 小时前
【SpringSecurity新手村系列】(7)基于资源权限码(Authority)的接口权限控制实战
java·spring boot·安全
Cathy Bryant2 小时前
微分几何:度规和高斯曲率
笔记·高等数学·物理·微分几何
ECT-OS-JiuHuaShan3 小时前
哲学的本质,是递归因果
java·开发语言·人工智能·科技·算法·机器学习·数学建模
梁山1号3 小时前
喷火器调节笔记
笔记
倾听一世,繁花盛开3 小时前
Java语言程序设计——篇十三(1)
java·开发语言·ide·eclipse
大腕先生3 小时前
通用分页超详细介绍(附带源代码解析&页面展示效果)
xml·java·linux·服务器·开发语言·前端·idea