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 篇,由浅入深持续更新中。有疑问或想深挖的源码点,评论区告诉我,下篇见。

相关推荐
Seven971 小时前
AI杂谈:别再问AI会不会替代你,先看你是不是驾驶员
人工智能·后端
lisin-lee-cooper1 小时前
JVM知识体系
java·jvm
ERD Online1 小时前
我们怎么设计 good first issue:让第一个 PR 两小时内合入
数据库·git·后端·开源·issue
(轻舟已过万重山)1 小时前
第40章 Spring AI 实战:企业级 AI 应用架构
人工智能·spring·架构
IT_陈寒2 小时前
Vite热更新失效?我的几个犯傻操作害我debug两小时
前端·人工智能·后端
凤山老林2 小时前
Spring Boot 大文件处理实战:分片上传、断点续传与 OSS 集成
java·spring boot·后端·大文件上传·分片上传·断点续传
敲个大西瓜2 小时前
JAVA 并发编程
java·并发编程
卷无止境2 小时前
Python 依赖管理这件事,到底该看哪个文件
后端·python
vHelios2 小时前
【电商项目】商品服务模块的问题解决与代码逻辑思考
java·sql·mybatis