Spring 源码系列(17): HandlerMapping 与 HandlerAdapter 两大体系

📌 阅读前提示 :第 16 篇看到 doDispatchgetHandlergetHandlerAdapter。本篇拆开这两大体系的内部结构------@RequestMapping 是如何变成一条条映射的?适配器又是如何反射调用你的 Controller 方法的?这是 MVC 源码「承上启下」的一篇。


一、引子:为什么需要「Mapping」和「Adapter」两个东西

初学者常混淆:HandlerMapping 找到了方法,为什么还要 HandlerAdapter 才能执行?

原因在解耦 :Spring 要支持多种 Handler 类型(@RequestMapping 方法、实现 Controller 接口的类、HttpRequestHandler、甚至函数式端点)。Mapping 只负责「把 URL 映射到某个 Handler 对象」,但「怎么调用这个 Handler」因类型而异------这部分交给 Adapter。这是典型的适配器模式


二、HandlerMapping 体系

2.1 继承结构

复制代码
HandlerMapping (接口)
   ├─ AbstractHandlerMapping(模板方法,含拦截器链、排序)
   │    ├─ AbstractHandlerMethodMapping  ← 处理「方法级」映射
   │    │    └─ RequestMappingHandlerMapping ★(@RequestMapping 主力)
   │    └─ AbstractUrlHandlerMapping      ← 处理「类级」URL 映射
   │         └─ SimpleUrlHandlerMapping / BeanNameUrlHandlerMapping
   └─ RouterFunctionMapping(函数式端点)

2.2 @RequestMapping 如何被注册

在容器启动的 afterPropertiesSet 阶段(第 7 篇生命周期)RequestMappingHandlerMapping 扫描所有 Bean:

java 复制代码
// RequestMappingHandlerMapping 继承自 AbstractHandlerMethodMapping
@Override
public void afterPropertiesSet() {
    initHandlerMethods();   // ← 初始化时扫描所有 @RequestMapping
}

protected void initHandlerMethods() {
    for (String beanName : getCandidateBeanNames()) {
        if (beanType != null && isHandler(beanType)) {        // isHandler: 类上有 @Controller 或 @RequestMapping
            detectHandlerMethods(beanName);                   // 解析每个方法上的 @RequestMapping
        }
    }
}

// detectHandlerMethods 内部:把 Method + 映射条件 注册进 MappingRegistry
// 最终形成:RequestCondition → HandlerMethod 的查找表

💡 重点@RequestMapping 信息被解析成 RequestMappingInfo(含 path、method、params、headers 等条件),连同 HandlerMethod(方法 + Bean 引用)一起存进 MappingRegistry。请求来时 getHandler 就是在这张表里匹配。

2.3 getHandler 匹配逻辑

java 复制代码
// AbstractHandlerMethodMapping.getHandlerInternal
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    // 1. 按 URL 查
    List<Match> matches = new ArrayList<>();
    addMatchingMappings(directPathMatches, matches, request);
    // 2. 按 RequestMappingInfo 的条件(method/params/headers)再过滤
    if (matches.isEmpty()) { /* 找不到 → 可能抛出 Ambiguous / No mapping */ }
    // 3. 多匹配时按规则排序取最优(如更具体的 path 优先)
    Match bestMatch = matches.get(0);
    return bestMatch.handlerMethod;
}

三、HandlerAdapter 体系

3.1 主要适配器

适配器 支持的 Handler 类型
RequestMappingHandlerAdapter @RequestMapping 标注的方法(最常用)
HttpRequestHandlerAdapter 实现 HttpRequestHandler 的类
SimpleControllerHandlerAdapter 实现老式 Controller 接口的类
HandlerFunctionAdapter 函数式端点 HandlerFunction

3.2 RequestMappingHandlerAdapter 如何反射调用

java 复制代码
// RequestMappingHandlerAdapter.java
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    return invokeHandlerMethod(request, response, (HandlerMethod) handler); // 核心
}

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) {
    // 1. 用 HandlerMethod 创建 ServletInvocableHandlerMethod
    ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    // 2. 注入参数解析器、返回值处理器(第18篇详解)
    invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    // 3. 真正反射调用
    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    return getModelAndView(mavContainer, modelFactory, webRequest);
}

📌 结论HandlerMethod 里保存了目标 Bean 引用 + Method 对象;适配器用反射 method.invoke(bean, args) 完成调用,参数 args参数解析器 填充(第 18 篇),返回值由返回值处理器处理。


四、Mapping 与 Adapter 体系图


五、常见误区

误区 正解
一个 URL 只能对应一个方法 可以有多匹配,按 RequestMappingInfo 条件排序取最优;条件冲突才报错
Adapter 负责找 Handler 不,Mapping 找 Handler,Adapter 只负责「调用」
@RequestMapping 运行时才解析 不,容器启动 afterPropertiesSet 阶段就扫描注册进 MappingRegistry
HandlerMethod 持有的是 Class 不,持有的是Bean 实例引用 + Method ,反射时直接 invoke(bean, args)
所有请求都走 RequestMappingHandlerMapping 仅注解 Controller;老式 Controller 接口走 SimpleControllerHandlerAdapter

🧪 面试题自测

  1. HandlerMapping 和 HandlerAdapter 为什么是两个体系?各自职责?
  2. @RequestMapping 在什么时机被解析注册?存在哪里?
  3. RequestMappingInfo 包含哪些匹配条件?
  4. 多个方法匹配同一 URL 时 Spring 如何取舍?
  5. RequestMappingHandlerAdapter 内部反射调用的关键对象是什么?
  6. 参数和返回值是谁处理的?(引出第 18 篇)

🔧 Debug 小技巧

RequestMappingHandlerMapping.afterPropertiesSet 断点,观察 MappingRegistryregistry 的映射条目数量随你写的 Controller 增长;再在 RequestMappingHandlerAdapter.invokeHandlerMethod 断点,看 invocableMethod 里的 beanmethod 如何被反射调用。


下一篇预告

第 18 篇:参数解析器 HandlerMethodArgumentResolver 与返回值处理器 HandlerMethodReturnValueHandler------你的 @RequestParam@RequestBody@PathVariable 究竟是怎么被塞进方法参数的?返回 String / ModelAndView / @ResponseBody 又如何被处理?


如果这篇对你有帮助,欢迎 点赞 · 收藏 · 关注 三连支持。

Spring 源码系列共 30 篇,由浅入深持续更新中。有疑问或想深挖的源码点,评论区告诉我,下篇见。

相关推荐
2601_962055975 小时前
跟据spring boot版本,查看对应的tomcat,并查看可支持的tomcat的版本范围
spring boot·后端·tomcat
EXI-小洲5 小时前
Java 操作 Word:字符串替换、图片插入、动态生成表格与API接口下载
java·开发语言·spring boot·word
2601_961901706 小时前
SpringBoot使用Nacos进行application.yml配置管理
java·spring boot·spring
Lost of 程序猿6 小时前
ASP.NET Core API 幂等性设计深度实战:从一次重复申领事故说起
后端·asp.net
何以解忧,唯有..7 小时前
LangChain 工具调用(Tool Calling)实战指南
java·前端·langchain
QQ_21696290967 小时前
【源码编号:project79475】SpringBoot校内二手交易平台:商品发布、分类检索、留言交流、订单管理全流程实战
java·spring boot·后端
前端 贾公子8 小时前
第09章:上下文与记忆 (2)
java·服务器·前端
2601_962203518 小时前
Java进阶07集合(续)
java·开发语言
郑州光合科技余经理9 小时前
本地生活服务系统:模块边界与结算字段怎么拆
java·开发语言·前端·后端·系统架构·uni-app·php
IT_陈寒9 小时前
Vue的嵌套组件竟然吃掉了我的事件?
前端·人工智能·后端