📝 导言: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的核心任务流程如下:
- 请求进入:接收
HttpServletRequest。 - 查找处理器:调用
HandlerMapping.getHandler(request)。 - 获取执行链:获得
HandlerExecutionChain(包含处理器和拦截器)。 - 执行处理:通过
HandlerAdapter实际执行处理器。 - 视图解析:处理结果并渲染视图。
2. HandlerMapping:请求映射的策略接口
HandlerMapping是一个策略接口,它定义了根据请求查找处理器执行链的能力。Spring MVC 可以配置多个HandlerMapping实现,它们会按照特定的顺序(Order属性)依次尝试查找处理器。
| 接口/实现 | 描述 | 关键方法 |
|---|---|---|
HandlerMapping | 核心策略接口,定义了查找处理器的方法。 | HandlerExecutionChain getHandler(HttpServletRequest request) |
RequestMappingHandlerMapping | Spring 3.1+ 默认且推荐的实现,专门处理基于@RequestMapping注解的处理器。 | 内部维护映射表 (Map<RequestMappingInfo, HandlerMethod>) |
3. RequestMappingHandlerMapping:注解驱动的核心
RequestMappingHandlerMapping是本文分析的重点。它是 Spring Boot/Spring MVC 默认用于处理@Controller和@RestController中@RequestMapping注解的组件。
它的工作可以清晰地划分为两大阶段:
- 初始化阶段 (Application Startup):扫描所有 Bean,解析
@RequestMapping,构建映射查找表。 - 请求处理阶段 (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:
- 标记了
@Controller的类。 - 标记了
@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对象中。
- 解析类级别注解:如果 Controller 类上也有
@RequestMapping(例如/api/v1),这部分信息首先被解析。 - 解析方法级别注解:方法上的
@RequestMapping(例如/users/{id})也被解析。 - 合并:类级别和方法级别的映射信息通过
combine()方法合并,形成最终的RequestMappingInfo。
RequestMappingInfo的结构:它实际上是多个条件的复合体,每个条件都抽象为一个RequestCondition接口的实现:内部组件 (Condition) 描述 对应 @RequestMapping属性PatternsRequestCondition路径匹配条件 value或pathRequestMethodsRequestConditionHTTP 方法条件 methodParamsRequestCondition请求参数条件 paramsHeadersRequestCondition请求头条件 headersConsumesRequestCondition客户端希望发送的数据类型( Content-Type)consumesProducesRequestCondition服务器希望返回的数据类型( Accept)produces
3.3 注册映射:构建核心查找表
最终,RequestMappingHandlerMapping调用核心方法registerHandlerMethod(),将解析得到的RequestMappingInfo和对应的HandlerMethod注册到其内部的查找表(mappingRegistry)中。
这个查找表的核心结构是一个Map<RequestMappingInfo, HandlerMethod>,它构成了请求映射的核心数据结构。
4. 潜在冲突检测
在注册过程中,RequestMappingHandlerMapping会检查是否存在重复映射。如果两个不同的HandlerMethod解析出了完全相同的RequestMappingInfo(即路径、方法、参数等所有条件都一样),Spring 容器将抛出异常,阻止应用启动,确保路由的唯一性。
III. 请求处理阶段:从请求到执行链的构建
当应用启动完成,内部映射表构建完毕后,每一次客户端请求的到来,都会触发RequestMappingHandlerMapping的运行时查找逻辑。
1.DispatcherServlet调用getHandler()
当一个 HTTP 请求到达DispatcherServlet的doService()或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):
- 路径精确度:优先匹配更精确的路径(例如
/users/new比/users/{id}优先级高)。 - 路径变量数量:路径变量(
{id})越少的优先级越高。 - HTTP 方法精确度:匹配特定 HTTP 方法(
@GetMapping)的比匹配所有方法(未指定method)的优先级高。 - 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,并将以下两个关键元素放入其中:
- Handler:确定的
HandlerMethod对象。 - 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,创建RequestMappingInfo和HandlerMethod。 | 核心查找表(Map<RequestMappingInfo, HandlerMethod>) |
| 查找(Runtime) | RequestMappingHandlerMapping | 根据请求 URI/Method,遍历查找表,进行条件匹配,确定最佳匹配的HandlerMethod。 | 确定的HandlerMethod |
| 构建链(Runtime) | RequestMappingHandlerMapping | 查找适用的HandlerInterceptor,将HandlerMethod与HandlerInterceptor封装。 | HandlerExecutionChain(返回给DispatcherServlet) |
| 执行(Runtime) | DispatcherServlet&HandlerAdapter | 收到HandlerExecutionChain后,先依次调用拦截器的preHandle(),然后通过HandlerAdapter实际调用HandlerMethod。 | ModelAndView或null |
1. HandlerAdapter 的协作
HandlerExecutionChain返回后,DispatcherServlet找到对应的HandlerAdapter(通常是RequestMappingHandlerAdapter)来执行处理器。
- 拦截器前置处理:
DispatcherServlet依次调用执行链中所有拦截器的preHandle()方法。 - 执行处理器:
RequestMappingHandlerAdapter接收HandlerMethod,利用反射机制调用其核心方法invokeAndHandle()。 - 参数解析:在调用方法前,
HandlerAdapter负责处理方法参数:- 读取请求中的路径变量、查询参数、请求体等。
- 通过
HandlerMethodArgumentResolver体系(如PathVariableMethodArgumentResolver、RequestResponseBodyMethodProcessor)将数据绑定到方法的参数上。
- 拦截器后置/完成:处理器执行后,依次调用
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. 映射信息与处理器关系
你好! 这是你第一次使用Markdown编辑器所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
新的改变
我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:
- 全新的界面设计,将会带来全新的写作体验;
- 在创作中心设置你喜爱的代码高亮样式,Markdown将代码片显示选择的高亮样式进行展示;
- 增加了图片拖拽功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
- 全新的KaTeX数学公式语法;
- 增加了支持甘特图的mermaid语法1功能;
- 增加了多屏幕编辑Markdown文章功能;
- 增加了焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置等功能,功能按钮位于编辑区域与预览区域中间;
- 增加了检查列表功能。
功能快捷键
撤销: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
- 项目2
- 项目3
- 计划任务
- 完成任务
创建一个表格
一个简单的表格是这么创建的:
| 项目 | Value |
|---|---|
| 电脑 | $1600 |
| 手机 | $12 |
| 导管 | $1 |
设定内容居中、居左、居右
使用:---------:居中
使用:----------居左
使用----------:居右
| 第一列 | 第二列 | 第三列 |
|---|---|---|
| 第一列文本居中 | 第二列文本居右 | 第三列文本居左 |
SmartyPants
SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:
| TYPE | ASCII | HTML |
|---|---|---|
| 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)=(n−1)!∀n∈N是通过欧拉积分
Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,.Γ(z)=∫0∞tz−1e−tdt.
你可以找到更多关于的信息LaTeX数学表达式here.
新的甘特图功能,丰富你的文章
- 关于甘特图语法,参考 这儿,
UML 图表
可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:
这将产生一个流程图。:
- 关于Mermaid语法,参考 这儿,
FLowchart流程图
我们依旧会支持flowchart的流程图:
- 关于Flowchart流程图语法,参考 这儿.
导出与导入
导出
如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到文章导出,生成一个.md文件或者.html文件进行本地保存。
导入
如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。
mermaid语法说明 ↩︎
注脚的解释 ↩︎