从01开始 从01开始
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)

peterjxl

人生如逆旅,我亦是行人
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)
  • 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完整启动过程
      • SpringBoot
  • Java并发

  • Java源码

  • JVM

  • 韩顺平

  • Java
  • 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:

​​

‍

‍

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

‍

‍

‍

在GitHub上编辑此页 (opens new window)
上次更新: 2023/8/23 10:10:57
MatrixVariable:矩阵变量
Servlet-API参数解析原理

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

Theme by Vdoing | Copyright © 2022-2023 粤ICP备2022067627号-1 粤公网安备 44011302003646号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式