news 2025/12/20 20:43:08

【Spring MVC路由篇】从`@RequestMapping`注解到执行链(`HandlerExecutionChain`)的构建过程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Spring MVC路由篇】从`@RequestMapping`注解到执行链(`HandlerExecutionChain`)的构建过程

📝 导言:Spring MVC 路由的基石

在现代 Web 开发中,Spring MVC(Model-View-Controller)框架以其灵活、强大的特性占据了主导地位。Spring MVC 的核心机制之一是请求路由(Request Routing),它决定了传入的 HTTP 请求应该由哪个业务处理器(通常是 Controller 中的方法)来处理。

这一核心功能主要由HandlerMapping体系负责实现。本文将深入剖析 Spring MVC 是如何利用@RequestMapping注解,在应用启动时构建内部映射表,并在请求到达时高效地将 URL 匹配到正确的处理器,并最终构建出完整的HandlerExecutionChain(处理器执行链)的全过程。理解这一机制,是掌握 Spring MVC 底层工作原理的关键。


I. 核心概念与架构概览

在开始深入细节之前,我们首先需要理解涉及到的几个关键角色和它们在 Spring MVC 架构中的位置。

1. DispatcherServlet:路由的协调者

DispatcherServlet是 Spring MVC 的前端控制器,是所有请求的入口。它不直接处理请求映射,而是将这个任务委托给HandlerMapping策略接口的实现类。

当一个请求到达时,DispatcherServlet的核心任务流程如下:

  1. 请求进入:接收HttpServletRequest
  2. 查找处理器:调用HandlerMapping.getHandler(request)
  3. 获取执行链:获得HandlerExecutionChain(包含处理器和拦截器)。
  4. 执行处理:通过HandlerAdapter实际执行处理器。
  5. 视图解析:处理结果并渲染视图。

2. HandlerMapping:请求映射的策略接口

HandlerMapping是一个策略接口,它定义了根据请求查找处理器执行链的能力。Spring MVC 可以配置多个HandlerMapping实现,它们会按照特定的顺序(Order属性)依次尝试查找处理器。

接口/实现描述关键方法
HandlerMapping核心策略接口,定义了查找处理器的方法。HandlerExecutionChain getHandler(HttpServletRequest request)
RequestMappingHandlerMappingSpring 3.1+ 默认且推荐的实现,专门处理基于@RequestMapping注解的处理器。内部维护映射表 (Map<RequestMappingInfo, HandlerMethod>)

3. RequestMappingHandlerMapping:注解驱动的核心

RequestMappingHandlerMapping是本文分析的重点。它是 Spring Boot/Spring MVC 默认用于处理@Controller@RestController@RequestMapping注解的组件。

它的工作可以清晰地划分为两大阶段:

  1. 初始化阶段 (Application Startup):扫描所有 Bean,解析@RequestMapping,构建映射查找表
  2. 请求处理阶段 (Runtime):根据传入的请求 URL 和 HTTP 方法,查询映射表,返回HandlerExecutionChain

II. 初始化阶段:构建映射查找表

RequestMappingHandlerMapping的初始化过程是 Spring MVC 路由机制的基石。这一过程发生在应用启动时,通过扫描和解析注解,将逻辑映射转化为高效的查找结构。

1. Bean 生命周期与初始化入口

RequestMappingHandlerMapping实现了 Spring 的InitializingBean接口或继承了其父类AbstractHandlerMethodMapping的相关初始化逻辑。

核心入口方法:afterPropertiesSet()(或其内部调用的initHandlerMethods())。

在 Spring 容器完成 Bean 的实例化和属性设置后,该方法被调用,启动以下流程:

  • 获取 Spring 容器中所有的 Bean 名称。
  • 遍历这些 Bean,筛选出合适的处理器 Bean。

2. 筛选处理器 Bean

RequestMappingHandlerMapping主要关注两类 Bean:

  1. 标记了@Controller的类。
  2. 标记了@RestController的类@RestController内部包含@Controller)。

它通过is />抽象方法来判断一个 Bean 是否符合条件,通常是检查其类定义上是否存在@Controller注解。

3. 解析@RequestMapping注解:HandlerMethod 的诞生

对于每一个筛选出的处理器 Bean,RequestMappingHandlerMapping会迭代检查其类级别方法级别上的所有方法。

3.1 封装 HandlerMethod

当发现方法上存在@RequestMapping(或其变体如@GetMapping@PostMapping等)时,该方法就被确定为一个可处理请求的处理器方法(Handler Method)

  • HandlerMethod是 Spring MVC 对处理器方法的抽象封装。它包含:
    • bean:处理器实例(Controller 对象)。
    • method:Java 反射中的Method对象,用于后续实际调用。
    • beanType:处理器类的类型。
3.2 解析并合并 RequestMappingInfo

HandlerMethod对应的映射条件被封装在RequestMappingInfo对象中。

  1. 解析类级别注解:如果 Controller 类上也有@RequestMapping(例如/api/v1),这部分信息首先被解析。
  2. 解析方法级别注解:方法上的@RequestMapping(例如/users/{id})也被解析。
  3. 合并:类级别和方法级别的映射信息通过combine()方法合并,形成最终的RequestMappingInfo
  • RequestMappingInfo的结构:它实际上是多个条件的复合体,每个条件都抽象为一个RequestCondition接口的实现:

    内部组件 (Condition)描述对应@RequestMapping属性
    PatternsRequestCondition路径匹配条件valuepath
    RequestMethodsRequestConditionHTTP 方法条件method
    ParamsRequestCondition请求参数条件params
    HeadersRequestCondition请求头条件headers
    ConsumesRequestCondition客户端希望发送的数据类型(Content-Typeconsumes
    ProducesRequestCondition服务器希望返回的数据类型(Acceptproduces
3.3 注册映射:构建核心查找表

最终,RequestMappingHandlerMapping调用核心方法registerHandlerMethod(),将解析得到的RequestMappingInfo和对应的HandlerMethod注册到其内部的查找表(mappingRegistry)中。

这个查找表的核心结构是一个Map<RequestMappingInfo, HandlerMethod>,它构成了请求映射的核心数据结构

4. 潜在冲突检测

在注册过程中,RequestMappingHandlerMapping会检查是否存在重复映射。如果两个不同的HandlerMethod解析出了完全相同RequestMappingInfo(即路径、方法、参数等所有条件都一样),Spring 容器将抛出异常,阻止应用启动,确保路由的唯一性。


III. 请求处理阶段:从请求到执行链的构建

当应用启动完成,内部映射表构建完毕后,每一次客户端请求的到来,都会触发RequestMappingHandlerMapping的运行时查找逻辑。

1.DispatcherServlet调用getHandler()

当一个 HTTP 请求到达DispatcherServletdoService()doDispatch()方法时,它会按顺序遍历已配置的HandlerMapping列表,并调用其核心方法:

HandlerExecutionChain g e t H a n d l e r ( HttpServletRequest r e q u e s t ) \text{HandlerExecutionChain} \ getHandler(\text{HttpServletRequest}\ request)HandlerExecutionChaingetHandler(HttpServletRequestrequest)

2. 匹配逻辑:lookupHandlerMethod()

RequestMappingHandlerMapping内部的查找逻辑主要集中在lookupHandlerMethod()方法中。

2.1 提取查找路径

首先,它从HttpServletRequest中提取出用于路径匹配的 URI。

2.2 遍历与匹配(RequestCondition.getMatchingCondition())

HandlerMapping遍历其内部查找表中的所有RequestMappingInfo(Key)。对于每一个RequestMappingInfo,它会执行一个关键步骤:

  • 条件匹配:依次调用RequestMappingInfo内部的各个RequestCondition(路径、方法、参数等)的getMatchingCondition(request)方法。

    • 如果请求满足某个条件,该条件会返回一个经过处理的、可能包含路径变量信息的新的RequestCondition实例。
    • 如果请求不满足某个条件(例如 HTTP 方法不匹配),匹配过程会立即终止,该RequestMappingInfo被跳过。
  • 最终匹配:只有当一个RequestMappingInfo内部的所有条件都匹配成功时,它才被视为一个候选匹配

2.3 最佳匹配的确定(Comparator)

在一个复杂的应用中,一个请求 URI 可能会同时匹配多个RequestMappingInfo(例如/users/{id}/users/new)。Spring MVC 必须决定哪个是最佳匹配

RequestMappingInfo实现了Comparable接口,它定义了一套优先级规则Comparator):

  1. 路径精确度:优先匹配更精确的路径(例如/users/new/users/{id}优先级高)。
  2. 路径变量数量:路径变量({id})越少的优先级越高。
  3. HTTP 方法精确度:匹配特定 HTTP 方法(@GetMapping)的比匹配所有方法(未指定method)的优先级高。
  4. Consumes/Produces:匹配特定Content-Type/Accept的优先级高。

通过compareTo()方法,RequestMappingHandlerMapping会对所有候选匹配进行排序,选择优先级最高的那个对应的HandlerMethod

3. 构建 HandlerExecutionChain:整合拦截器

一旦最佳匹配的HandlerMethod被确定,RequestMappingHandlerMapping的下一个任务就是构建HandlerExecutionChain

HandlerExecutionChain是 Spring MVC 灵活性的关键所在,它是一个**处理器(Handler)和一系列拦截器(HandlerInterceptor)**组成的链式结构。

3.1 确定 HandlerInterceptor

HandlerMapping会根据当前的请求路径,从配置的拦截器列表中(通常在配置类中通过addInterceptors()方法配置)找出所有适用的拦截器。

  • 路径匹配:拦截器通常配置了includePatterns(包含路径)和excludePatterns(排除路径)。HandlerMapping会检查请求 URI 是否符合这些模式。
3.2 实例化 HandlerExecutionChain

HandlerMapping实例化HandlerExecutionChain,并将以下两个关键元素放入其中:

  1. Handler:确定的HandlerMethod对象。
  2. Interceptors:确定的一组HandlerInterceptor实例。

4. 返回执行链与路径变量存储

最终,RequestMappingHandlerMapping将构建好的HandlerExecutionChain返回给DispatcherServlet

路径变量(Path Variables)的处理:

在匹配过程中,如果路径包含变量(如/users/{id}),RequestMappingHandlerMapping会解析出{id}对应的值,并将其存储在HttpServletRequest请求属性(Attributes)中。

  • 存储位置:键值对以URI_TEMPLATE_VARIABLES_ATTRIBUTE(常量)作为 Key,Map<String, String>作为 Value 存储在HttpServletRequest中。
  • 后续使用:在处理器调用阶段,HandlerAdapter会从请求属性中读取这些变量,并通过@PathVariable注解注入到 Controller 方法参数中。

IV. 流程总结与扩展:HandlerAdapter 的协作

阶段关键角色核心动作产出结果
初始化(Startup)RequestMappingHandlerMapping扫描@Controller,解析@RequestMapping,创建RequestMappingInfoHandlerMethod核心查找表(Map<RequestMappingInfo, HandlerMethod>)
查找(Runtime)RequestMappingHandlerMapping根据请求 URI/Method,遍历查找表,进行条件匹配,确定最佳匹配的HandlerMethod确定的HandlerMethod
构建链(Runtime)RequestMappingHandlerMapping查找适用的HandlerInterceptor,将HandlerMethodHandlerInterceptor封装。HandlerExecutionChain(返回给DispatcherServlet
执行(Runtime)DispatcherServlet&HandlerAdapter收到HandlerExecutionChain后,先依次调用拦截器的preHandle(),然后通过HandlerAdapter实际调用HandlerMethodModelAndViewnull

1. HandlerAdapter 的协作

HandlerExecutionChain返回后,DispatcherServlet找到对应的HandlerAdapter(通常是RequestMappingHandlerAdapter)来执行处理器。

  1. 拦截器前置处理:DispatcherServlet依次调用执行链中所有拦截器的preHandle()方法。
  2. 执行处理器:RequestMappingHandlerAdapter接收HandlerMethod,利用反射机制调用其核心方法invokeAndHandle()
  3. 参数解析:在调用方法前,HandlerAdapter负责处理方法参数:
    • 读取请求中的路径变量、查询参数、请求体等。
    • 通过HandlerMethodArgumentResolver体系(如PathVariableMethodArgumentResolverRequestResponseBodyMethodProcessor)将数据绑定到方法的参数上。
  4. 拦截器后置/完成:处理器执行后,依次调用postHandle()afterCompletion()

2. 拦截器执行顺序

HandlerExecutionChain保证了拦截器按照注册顺序(Order属性)执行,但前置处理和后置处理的顺序是相反的:

  • 前置处理 (preHandle):按照注册顺序依次执行。如果任何一个preHandle返回false,链条中断。
  • 后置处理 (postHandle):按照注册顺序的逆序依次执行。
  • 完成处理 (afterCompletion):按照注册顺序的逆序依次执行。

3. 总结

HandlerMapping机制是 Spring MVC 路由功能的心脏。它将开发者友好的@RequestMapping注解,经过精心设计的分阶段处理(初始化时的映射表构建和运行时的最佳匹配算法),高效地转化为一个可执行的、包含了所有前置/后置逻辑的HandlerExecutionChain。这一过程确保了请求能够被准确、灵活且高性能地路由到正确的业务代码上,构成了 Spring MVC 强大路由能力的基础。


📚 附录:核心组件接口与类关系

为了更全面地理解这一过程,下面列出关键接口与实现类的关系(使用 UML 风格标记)。

1. 核心抽象类继承关系

classDiagram direction TB interface HandlerMapping { +getHandler(request): HandlerExecutionChain } AbstractHandlerMapping <|-- HandlerMapping abstract class AbstractHandlerMapping { #getHandlerInternal(request): Object +getHandler(request): HandlerExecutionChain +setInterceptors(interceptors: List<HandlerInterceptor>) } AbstractHandlerMethodMapping <|-- AbstractHandlerMapping abstract class AbstractHandlerMethodMapping { -mappingRegistry: MappingRegistry #initHandlerMethods() #lookupHandlerMethod(lookupPath, request): HandlerMethod +afterPropertiesSet() } RequestMappingHandlerMapping --|> AbstractHandlerMethodMapping class RequestMappingHandlerMapping { +isHandler(beanType): boolean +get : RequestMappingInfoHandlerMethod }

2. 映射信息与处理器关系

1
*
1
*
uses
1
1
contains
1
1
contains
1
*
RequestMappingInfo
-patternsCondition: PatternsRequestCondition
-methodsCondition: RequestMethodsRequestCondition
// ... more conditions
+getMatchingCondition(request)
+compareTo(other)
HandlerMethod
+bean: Object
+method: Method
+getMethodParameters()
MappingRegistry
+Map registry
RequestMappingHandlerMapping
HandlerExecutionChain
+handler: Object
+interceptors: HandlerInterceptor[]
+getHandler()
+getInterceptors()
HandlerInterceptor

你好! 这是你第一次使用Markdown编辑器所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown将代码片显示选择的高亮样式进行展示;
  3. 增加了图片拖拽功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的KaTeX数学公式语法;
  5. 增加了支持甘特图的mermaid语法1功能;
  6. 增加了多屏幕编辑Markdown文章功能;
  7. 增加了焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了检查列表功能。

功能快捷键

撤销:Ctrl/Command+Z
重做:Ctrl/Command+Y
加粗:Ctrl/Command+B
斜体:Ctrl/Command+I
标题:Ctrl/Command+Shift+H
无序列表:Ctrl/Command+Shift+U
有序列表:Ctrl/Command+Shift+O
检查列表:Ctrl/Command+Shift+C
插入代码:Ctrl/Command+Shift+K
插入链接:Ctrl/Command+Shift+L
插入图片:Ctrl/Command+Shift+G
查找:Ctrl/Command+F
替换:Ctrl/Command+G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本强调文本

加粗文本加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210运算结果是 1024.

插入链接与图片

链接: link.

图片:

带尺寸的图片:

居中的图片:

居中并且带尺寸的图片:

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的代码片.

// An highlighted blockvarfoo='bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目Value
电脑$1600
手机$12
导管$1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列第二列第三列
第一列文本居中第二列文本居右第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPEASCIIHTML
Single backticks'Isn't this fun?'‘Isn’t this fun?’
Quotes"Isn't this fun?"“Isn’t this fun?”
Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to-HTMLconversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb NΓ(n)=(n1)!nN是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,.Γ(z)=0tz1etdt.

你可以找到更多关于的信息LaTeX数学表达式here.

新的甘特图功能,丰富你的文章

2014-01-072014-01-092014-01-112014-01-132014-01-152014-01-172014-01-192014-01-21已完成进行中计划一计划二现有任务Adding GANTT diagram functionality to mermaid
  • 关于甘特图语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:

张三李四王五你好!李四, 最近怎么样?你最近怎么样,王五?我很好,谢谢!我很好,谢谢!李四想了很长时间, 文字太长了不适合放在一行.打量着王五...很好... 王五, 你怎么样?张三李四王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于Mermaid语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

Created with Raphaël 2.3.0开始我的操作确认?结束yesno
  • 关于Flowchart流程图语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到文章导出,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/12 16:08:54

Vue Konva实战手册:构建高效画布应用的完全攻略

Vue Konva实战手册&#xff1a;构建高效画布应用的完全攻略 【免费下载链接】vue-konva Vue & Canvas - JavaScript library for drawing complex canvas graphics using Vue. 项目地址: https://gitcode.com/gh_mirrors/vu/vue-konva Vue Konva作为Vue.js生态中专业…

作者头像 李华
网站建设 2025/12/20 4:02:42

GPTBots.ai:从零开始构建企业级AI智能体,无需代码经验

GPTBots.ai是一款企业级AI Agent无代码构建平台&#xff0c;提供Agent、FlowAgent和MultiAgent三种智能体类型&#xff0c;解决LLM幻觉、垂直知识缺乏等痛点。平台支持RAG系统、工作流编排、工具集成&#xff0c;可快速构建企业级AI应用&#xff0c;无需代码经验即可上手&#…

作者头像 李华
网站建设 2025/12/12 16:08:20

洗车行业小程序源码系统一体化智能后台,让管理清晰高效

温馨提示&#xff1a;文末有资源获取方式痛点一&#xff1a;客户来无影去无踪&#xff0c;复购率低&#xff1f; 解决方案&#xff1a;构建私域会员池&#xff0c;实现精准触达。 系统内置完整的会员卡体系。您可以设置不同等级的会员卡&#xff08;如次卡、月卡、年卡&#xf…

作者头像 李华
网站建设 2025/12/17 21:57:24

为什么说PHP程序员一定要学会自我慈悲?

“自我慈悲”不是自我放纵或降低标准&#xff0c;而是在一个高压力、快迭代、强批判的行业里&#xff0c;建立内在的稳定系统和可持续的燃料补给机制。第一部分&#xff1a;为什么PHP程序员是“自我苛责”的重灾区&#xff1f;技术生态的“原罪”与鄙视链压力 PHP长期处于技术圈…

作者头像 李华
网站建设 2025/12/17 21:57:22

Blender终极指南:如何快速导入虚幻引擎PSK和PSA文件

Blender终极指南&#xff1a;如何快速导入虚幻引擎PSK和PSA文件 【免费下载链接】io_scene_psk_psa A Blender plugin for importing and exporting Unreal PSK and PSA files 项目地址: https://gitcode.com/gh_mirrors/io/io_scene_psk_psa 想要在Blender中无缝使用虚…

作者头像 李华