从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

    • SpringBoot 教程-尚硅谷

      • SpringBoot 课程介绍
      • Spring 和 SpringBoot
      • HelloWorld
      • 了解自动配置原理
      • 底层注解-@Configuration 详解
      • 底层注解-@Import 导入组件
      • 底层注解-@Conditional 条件装配
      • 原生配置文件引入-@ImportResource
      • 底层注解-配置绑定 @ConfigurationProperties
      • 自动配置原理
      • 自动配置流程
      • Lombok 简化开发
      • DevTools
      • Spring-Initailizr
      • 配置文件-Yaml 用法
      • Web 开发简介
      • web 开发-静态资源规则于定制化
      • 静态资源配置原理
      • Rest 映射及源码解析
      • 请求映射原理
      • 常用参数注解使用
      • MatrixVariable:矩阵变量
      • 各种类型参数解析原理
        • 从 DispatcherServlet 开始
        • HandlerAdapter
        • 参数解析器
        • 返回值处理器
        • 参数解析器的调用过程
        • 解析参数
      • Servlet-API 参数解析原理
      • Model、Map 参数解析原理
      • 自定义对象参数绑定原理
      • 自定义 Converter 原理
      • 数据响应原理
      • 内容协商原理
      • 基于请求参数的内容原理
      • 自定义 MessageConverter 原理
      • Thymeleaf 初体验
      • web 实验-后台管理系统
      • web 实验-抽取公共页面
      • web 实验-遍历数据
      • 源码分析-视图解析器与视图
      • 拦截器-登录检查与静态资源放行
      • 拦截器的执行时机和原理
      • 单文件和多文件上传的使用
      • 文件上传原理
      • 错误处理机制
      • 错误处理-底层组件源码分析
      • 异常处理流程
      • 几种异常处理原理
      • Web 原生对象注入
      • 嵌入式 Servlet 容器
      • 定制化原理
      • 数据库场景的自动配置分析和整合测试
      • 自定义方式整合 Druid
      • 通过 starter 整合 Druid
      • 整合 Mybatis
      • 使用注解整合 Mybatis
      • 整合 MybatisPlus 操作数据库
      • MybatisPlus-列表分页展示
      • 整合 Redis
      • 单元测试-Junit5
      • 单元测试-断言机制
      • 单元测试-前置条件
      • 单元测试-嵌套测试
      • 单元测试-参数化测试
      • 指标监控-基本概念
      • 指标监控-配置 EndPoint
      • 指标监控-可视化
      • 原理解析-Profile 功能
      • 配置文件深入
      • 自定义 Starter
      • SpringApplication 初始化过程
      • SpringBoot 完整启动过程
  • Java
  • SpringBoot
  • SpringBoot 教程-尚硅谷
2023-08-22
目录

各种类型参数解析原理

# 230.各种类型参数解析原理

前两篇博客讲了如何解析参数,现在来讲讲原理,首先讲讲如何解析参数并绑定到方法参数上。

注意:文章会比较长,可以慢慢看;或者先快速过一遍,有个印象,然后逐步分析

‍

# 从 DispatcherServlet 开始

DispatcherServlet 是处理请求的入口,我们在 doDispatch 方法上打个断点:

然后以 debug 方式运行,以 @PathVariable (opens new window) 的请求为例。之前的获取 handler 的过程我们已经讲过,就不重复了;下一步就是获取 HandlerAdapter:

‍

然后 SpringMVC,会通过反射获取到处理器方法,并且给方法参数绑定变量后,再调用处理器方法(第 1040 行左右):

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
1
2

‍ 这是一个比较麻烦的过程;因此 SpringMVC 会将其封装成了一个 Adapter。HandlerAdapter,其实是一个 SpringMVC 的接口:

package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;

public interface HandlerAdapter {
	boolean supports(Object handler);

	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
	long getLastModified(HttpServletRequest request, Object handler);

}
1
2
3
4
5
6
7
8
9
10
11
12
13

‍ supports 方法说明支持哪种 Handler,支持的话就调用 handle 方法来处理请求,未来我们也可以自定义 HandlerAdapter 来处理复杂的请求

# HandlerAdapter

接下来我们继续步入 getHandlerAdapter 方法:

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 [" + handler +
			"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
1
2
3
4
5
6
7
8
9
10
11

‍ 首先,会判断 handlerAdapters 集合是否为空,不为空则通过循环判断哪个集合里的元素能处理 handler;为空则抛出异常。

handlerAdapters 集合里有 4 个元素

‍

每个 Adapter 的含义:

  1. 支持方法上标注 @RequestMapping 的 Adapter
  2. 支持函数式编程的 Adapter
  3. ... 其他的暂且不表 ‍ 接下来就是执行 adapter.supports(handler),判断当前 adapter 是否支持当前 handler,我们步入进去:
@Override
public final boolean supports(Object handler) {
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
1
2
3
4

‍ 而我们的 handler,是 HandlerMethod 类型的(可以鼠标悬浮在变量上):

因此 if 判断通过,返回 adapter,也就是 RequestMappingHandlerAdapter。

返回 adapter 后,我们继续执行,直到 invoke 方法:

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
1
2

‍ 我们步入进去,最后会到 adapter 的 handleInternal 方法中:然后方法返回 ModelAndView

‍

然后之前是一些检查的代码,直到 792 行,是调用 handler 的方法,我们再次步入:

‍

# 参数解析器

往下执行,我们可以看到一个 argumentResolvers 的变量,该变量就是参数解析器:

‍

该参数解析器会有 26 个元素:里面就是确定要执行的目标方法,每个参数的值是什么

‍

比如

  • 第 0,1 个是解析@RequestParam 注解的
  • 第 2,3 个是解析@PathVariable 注解的
  • ...... ‍ argumentResolvers 变量,其父类实现了 HandlerMethodArgumentResolver 接口,该接口有 2 个方法:
public interface HandlerMethodArgumentResolver {

	boolean supportsParameter(MethodParameter parameter);

	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
1
2
3
4
5
6
7
8

‍ supportsParameter 方法:判断是否支持当前的参数;

resolveArgument 方法:支持的话,就调用该方法,解析该参数

# 返回值处理器

我们继续 debug 代码,回到 invokeHandlerMethod 方法,可以看到还有一个返回值处理器:

‍

也就是决定我们写的处理器方法,能返回什么类型,一共 15 种:例如 ModelAndView

‍

# 参数解析器的调用过程

接下来我们继续 debug,直到 876 行,调用方法:

我们步入进去:

第一行就执行 invokeForRequest 方法,该方法执行后就会调用处理器方法。我们可以其下一行和 Controller 上打一个断点,然后执行,可以看到会接下来就是执行我们自定义的处理器方法里的代码了:

‍

至此,参数解析是完成了的,但是具体怎么解析的呢?这就得看 invokeForRequest 方法做的事情了。

我们重新 debug,并发送请求,执行到该方法,并步入:

@Nullable
public Object invokeForRequest(NativeWebRequest request, 
    @Nullable ModelAndViewContainer mavContainer,
    Object... providedArgs) throws Exception {

    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
	logger.trace("Arguments: " + Arrays.toString(args));
    }
    return doInvoke(args);
}
1
2
3
4
5
6
7
8
9
10
11

第 6 行就是获取所有方法参数值。

我们继续步入,就会去到 InvocableHandlerMethod 类中,执行 getMethodArgumentValues 方法。首先方法会获取到所有参数的详细信息(这里是 3 个,并且每个参数的上面有什么注解,索引位置,类型是什么等),注意,此时并为设置好具体的值

‍

下一步,如果没有参数,则直接返回空:

if (ObjectUtils.isEmpty(parameters)) {
    return EMPTY_ARGS;
}
1
2
3

如果有参数,则通过 for 循环,逐步确定每个参数的值,然后返回:

Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
	MethodParameter parameter = parameters[i];
	parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
	args[i] = findProvidedArgument(parameter, providedArgs);
	if (args[i] != null) {
		continue;
	}
	if (!this.resolvers.supportsParameter(parameter)) {
		throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
	}
	try {
		args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
	}
	catch (Exception ex) {
		// Leave stack trace for later, exception may actually be resolved and handled...
		if (logger.isDebugEnabled()) {
			String exMsg = ex.getMessage();
			if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
				logger.debug(formatArgumentError(parameter, exMsg));
			}
		}
		throw ex;
	}
}
return args;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

‍ 接下来我们看看 for 循环体里的内容。首先第 9 行,会判断参数解析器是否支持当前的参数;如果不支持,则抛出异常;支持,则在第 13 行,会调用解析器的方法,来给参数赋值

那么如何判断是否支持参数的呢?我们步入:

@Override
public boolean supportsParameter(MethodParameter parameter) {
	return getArgumentResolver(parameter) != null;
}
1
2
3
4

‍ 然后再次步入:

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
	for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
		if (resolver.supportsParameter(parameter)) {
			result = resolver;
			this.argumentResolverCache.put(parameter, result);
			break;
		}
	}
    }
    return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

‍ 可以看到这是一个 for 循环,首先会从缓存中取出(一开始是空的,没有缓存),所以 result 是 null;然后就加载解析器,逐个判断 26 个参数解析器中是否支持当前参数,然后将解析器放到缓存中。这也就是为什么 SpringBoot 项目启动后,一开始有点慢,等来了几个请求之后,相关组件已经加载到内存中了,后续处理请求就快了。 ‍ 然后具体的解析器,是如何判断是否支持的呢?很简单,步入进去:

判断方法很简单,就是看当前参数的注解,是否和当前解析器的注解一样,不一样则说明不支持,继续寻找下一个解析器,最后就会找到矩阵变量的解析器

# 解析参数

拿到参数解析器后,我们步出,直到真正解析参数的位置:

步入:可以看到首先会获取当前参数的解析器,然后最后一行调用解析方法

‍

我们执行到最后一行,并步入:

首先会获取参数的信息,例如参数名(id)

下一步,就是解析参数的值了,我们步入进去:

‍

resolveName 源码如下:

@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
	Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
			HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
	return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}
1
2
3
4
5
6
7
8

‍ 还记得我们之前讲过的 UrlPathHelper 吗?它会将所有的路径变量解析出来,并保存到 request 域中,然后我们的参数解析器就可以直接从域中取值即可。例如:

‍

此时我们终于确定了第一个参数的值:3

‍

如果是解析 HTTP 请求头的参数解析器,其实底层用的也是 Servlet 原生的 API:

接下来就是一些默认处理了,这里不展开。

上次更新: 2025/6/3 09:31:54
MatrixVariable:矩阵变量
Servlet-API 参数解析原理

← MatrixVariable:矩阵变量 Servlet-API 参数解析原理→

最近更新
01
学点统计学:轻松识破一本正经的胡说八道
06-05
02
2025 年 5 月记
05-31
03
《贫穷的本质》很棒,但可能不适合你
05-27
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式