Spring MVC 参数解析(13)

news2024/9/21 12:37:42

目录

简介

调用流程

1. 首先,还是需要进行到前端控制器的doDispatch方法,这是我们的调用Spring MVC的核心入口方法

2. 在doDispatch方法内部,我们调用到了HandlerAdapter.handle(*****) 方法

3. 最终,我们会来到 RequestMappingHandlerAdapter 的 invokeHandlerMethod 方法;在这个方法内部,我们会设置 参数解析器 和 返回值解析器

4.  来到当前方法 invokeHandlerMethod 调用 Controller业务类处,进行业务类的调用;进行 参数解析 与 返回值处理

 5. 进入 ServletInvocableHandlerMethod 的 invokeAndHandle 方法处,进行参数解析 与 返回值处理

参数解析

case1:@RequestParam注解的参数解析器

case 2 :@PathVariable 注解的参数解析器


简介

据我所知:

参数解析器共分26种,针对不同的注解参数,有不同的参数解析器;

返回值解析器共分15种,针对不同的返回值,调用不同的返回值解析器;

其实,参数解析的核心代码在 HandlerMethodArgumentResolverCompositeresolveArgument 方法处,以后直接锁定这个方法就可以快速进行debug操作:

上一篇博客Spring MVC 的调用(12)_chen_yao_kerr的博客-CSDN博客已经对Spring MVC整体调用流程进行了梳理,但是在上一篇中关于HandlerAdapter调用到业务类,最终返回modelAndView的步骤中,我们只是梳理了整体流程, 并没有详细的分析如何进行参数传递的。本篇则是对上一篇的第五、六、七步中进行参数解析的补充.

其实,参数解析就是发生在HandlerAdapter调用到具体的业务类之前进行的,看下图:

调用流程

1. 首先,还是需要进行到前端控制器的doDispatch方法,这是我们的调用Spring MVC的核心入口方法

2. 在doDispatch方法内部,我们调用到了HandlerAdapter.handle(*****) 方法

3. 最终,我们会来到 RequestMappingHandlerAdapter 的 invokeHandlerMethod 方法;在这个方法内部,我们会设置 参数解析器 和 返回值解析器

参数解析器有26种:

返回值解析器:

 源码如下:

@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
			//获取数据绑定工厂  @InitBinder注解支持,没太多用
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			//Model工厂,收集了@ModelAttribute注解的方法
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

			//可调用的方法对象
			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			if (this.argumentResolvers != null) {
				//设置参数解析器
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
				//设置返回值解析器
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
			//设置参数绑定工厂
			invocableMethod.setDataBinderFactory(binderFactory);
			//设置参数名称解析类
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
			//调用有@ModelAttribute注解的方法。每次请求都会调用有@ModelAttribute注解的方法
			//把@ModelAttribute注解的方法的返回值存储到 ModelAndViewContainer对象的map中了
			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

			AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
			asyncWebRequest.setTimeout(this.asyncRequestTimeout);

			//异步处理
			WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
			asyncManager.setTaskExecutor(this.taskExecutor);
			asyncManager.setAsyncWebRequest(asyncWebRequest);
			asyncManager.registerCallableInterceptors(this.callableInterceptors);
			asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

			if (asyncManager.hasConcurrentResult()) {
				Object result = asyncManager.getConcurrentResult();
				mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
				asyncManager.clearConcurrentResult();
				LogFormatUtils.traceDebug(logger, traceOn -> {
					String formatted = LogFormatUtils.formatValue(result, !traceOn);
					return "Resume with async result [" + formatted + "]";
				});
				invocableMethod = invocableMethod.wrapConcurrentResult(result);
			}

			//Controller方法调用,重点看看
			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}

			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}

4.  来到当前方法 invokeHandlerMethod 调用 Controller业务类处,进行业务类的调用;进行 参数解析返回值处理

 5. 进入 ServletInvocableHandlerMethod invokeAndHandle 方法处,进行参数解析 与 返回值处理

参数解析

参数解析,需要针对不同的注解传递的参数进行解析,不能一概而论;首先来到参数解析的方法中。即 ServletInvocableHandlerMethod  类的 invokeForRequest 方法中:

	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);
	}

就是获取到所有的参数进行反射调用,反射调用在分析Spring源码的时候已经说过很多次了,就不再累赘了。下面针对不同的case分析不同的参数解析流程:

case1:@RequestParam注解的参数解析器

测试的业务方法

 @RequestMapping("/queryUser")
    public String queryUser(@RequestParam String language, HttpSession session) {
        session.setAttribute("language",language);
        return "ok";
    }

 URL: http://localhost:9090/user/queryUser?language=en

 测试结果:

源码分析

1、首先,肯定是来到参数解析的核心方法处

 2. 进入核心方法

    a, 首先,拿到当前方法对应的参数名称、参数类型、参数使用的注解、当前方法属于的哪个类、参数解析器等各种各样信息;

b.  进入参数解析的核心方法

 c. 根据参数获取对应的参数解析器

其实,就是根据步骤 a 获取到的 MethodParameter 对象,直接从缓存中拿到对应的参数解析器而已。拿到的参数解析器是 RequestParamMethodArgumentResolver。

d. 最后,自然是调用这个类进行参数解析了。具体调用的是 resolveArgument 方法进行 参数解析并返回数组了。

其实参数解析很简单,就是获取到参数的包装类,根据包装类获取到当前参数名、根据参数名到Request中获取参数值(此处就是调用 request.getParameterValues(name))

最终返回获取到的实际参数值并且返回。因为每个方法的参数可能是多个,因此我们是按照方法的参数进行遍历操作的,而且每个参数都是有对应下标的,这样返回值就不会乱、也不会少 。

case 2 :@PathVariable 注解的参数解析器

测试业务方法

    @RequestMapping("/queryUser2/{id}")
    public @ResponseBody String queryUser2(@PathVariable("id") String userId) {
        return "hello user :" + userId;
    }

URL:http://localhost:9090/user/queryUser2/1234

测试结果:

 源码分析:

文章开头就说了, 参数解析的核心代码在HandlerMethodArgumentResolverCompositeresolveArgument 方法处,直接把断点打在此处:

获取参数解析器,@PathVariable 对应的参数解析器为 PathVariableMethodArgumentResolver

根据参数解析器进行参数解析:

我们发现,这是相同的代码,进去以后,其实会调用父类的钩子方法,返回到当前参数解析器 PathVariableMethodArgumentResolver中调用钩子方法中

 其实,它就是根据拿 requet.getAttribute(***)获取参数值而已。所谓的Spring MVC,其实就是对 Servlet调用的封装而已。传统的 jsp + servlet调用,我们都是通过 request 直接拿参数的。而Spring mvc 自己封装了 request拿参数的过程而已。

下面列出常用的参数解析器以及对应的注解,我们直接看解析器的 resolveName 方法,就可以知道具体的解析过程。

  1. RequestHeaderMethodArgumentResolver   用来处理使用了 @RequestHeader 注解,并且参数类型不是 Map 的参数
  2. RequestHeaderMapMethodArgumentResolver 很明显,这个用来处理使用 @RequestHeader 注解,并且参数类型是 Map 的参数
  3. PathVariableMethodArgumentResolver  处理使用 @PathVariable 注解并且参数类型不为 Map 的参数
  4. PathVariableMapMethodArgumentResolver  处理使用 @PathVariable 注解并且参数类型为 Map 的参数
  5. RequestAttributeMethodArgumentResolver  处理使用 @RequestAttribute 注解的参数
  6. ServletCookieValueMethodArgumentResolver  处理使用 @CookieValue 的参数
  7. SessionAttributeMethodArgumentResolver  处理使用 @SessionAttribute 注解的参数
  8. RequestParamMethodArgumentResolver  这个功能就比较广了。使用了 @RequestParam 注解的参数、文件上传的类型 MultipartFile、或者一些没有使用任何注解的基本类型(Long、Integer)以及 String 等,都使用该参数解析器处理。变量不能是Map
  9. RequestParamMapMethodArgumentResolver   针对上一个,提供Map支持

至于不常用的参数解析器,按照我说的方法,直接找到以下方法打断点,就可以快速锁定:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/447929.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

完美解决丨2. `TypeError: list indices must be integers or slices, not str`

‘tuple’ object does not support item assignment 原因: tuple 是一个元素不可变的列表,如果尝试对 tuple 中的某个元素进行修改,会报错。 解决办法: 需要将 tuple 转换为 list,然后再把 list 转换为 tuple。 示例…

最长公共上升子序列LCIS

最长公共上升子序列LCIS 题目链接: acwing272. 最长公共上升子序列 题目描述: 输入输出: 题解: 首先考虑 最长上升子序列和 最长公共子序列问题 最长上升子序列的状态表示与状态转移如下: 状态表示: 用f[i],来表示以A[i]结尾的A[1~i]最长上升子序列的长度(注意,该上…

WPF教程(三)--事件Event调用

1、WPF应用程序的关闭 WPF应用程序的关闭只有在应用程序的 Shutdown 方法被调用时,应用程序才停止运行。 ShutDown 是隐式或显式发生,可以通过指定 ShutdownMode 的属性值来进行设置。 对ShutdownMode选项的更改,可以直接在App.xaml中更改&a…

coinex // 撮合引擎 逻辑流程 (两种数据源 初始化源和前端源)

目录 1 生产者 数据源 1.1. match-server 一启动 初始化数据 自动查询数据库 查询level2要展示的数据 1.2 match-server接收 前端发给Exchange-server的数据 2. 将查询/接受的数据EntrustOrder 转成 Order 解耦 过滤掉不要的属性 3.Order转成 OrderEvent 4. 分配序号发布…

【初学ROS,年轻人的第一个Node节点】

【初学ROS,年轻人的第一个Node节点】 1. 工作空间设置2. 创建Package3. 回访依赖包4. 创建Node节点5. 源码编译6. 运行Node节点7. Node节点完善8. 总结 本教程是B站阿杰视频的笔记 视频地址:https://www.bilibili.com/video/BV1nG411V7HW 超声波传感器 …

界面组件DevExtreme v22.2新版亮点 - 各UI组件增强升级

DevExtreme拥有高性能的HTML5 / JavaScript小部件集合,使您可以利用现代Web开发堆栈(包括React,Angular,ASP.NET Core,jQuery,Knockout等)构建交互式的Web应用程序。从Angular和Reac&#xff0c…

geometric distribution and exponential distribution(几何分布和指数分布)

几何分布 分布函数均值和方差意义 表示经过k次实验才第一次得到正确的实验结果 比如抛硬币得到正面的需要抛的次数 指数分布 分布函数均值和方差意义 表示经过一段x之后,某件事第一次发生 比如经过x时间之后,公交车来的概率 比如餐厅从开业到第一个客人…

基于ArcGIS 服务获取气象栅格某点位值及数据更新

1. 背景 假如有很多气象数据,不想通过后台脚本去获取数据,想通过前台服务的rest接口去识别,并且这些栅格数据可能是需要更新变化的,以下对一些技术方法做一个简单的介绍。 需求概述: 点击某一个点,获取影…

设计模式 --- 概述

一、设计模式概述 1.1、软件设计模式的产生背景 "设计模式"最初并不是出现在软件设计中,而是被用于建筑领域的设计中。 1977年美国著名建筑大师、加利福尼亚大学伯克利分校环境结构中心主任 克里斯托夫亚历山大 (Christopher Alexander&…

c/c++:三维数组,字符数组和字符串,统计字符串中字符出现的频次,scanf输入空格,正则匹配表达式

c/c:三维数组,字符数组和字符串,统计字符串中字符出现的频次,scanf输入空格,正则匹配表达式 2022找工作是学历、能力和运气的超强结合体,遇到寒冬,大厂不招人,此时学会c的话, 我所知…

WPF教程(十一)---数据绑定(4)--数据类绑定

一、排序 如果想以特定的方式对数据进行排序,可以绑定到 CollectionViewSource,而不是直接绑定到 ObjectDataProvider。CollectionViewSource 则会成为数据源,并充当截取 ObjectDataProvider 中的数据的媒介,并提供排序、分组和筛…

PMP项目管理-[第六章]进度管理

进度管理知识体系: 规划进度管理: 定义活动: 排列活动顺序: 估算活动持续时间: 制定进度计划: 6.1 规划进度管理 定义:为规划、编制、管理、执行和控制项目进度而制定政策、程序和文档的过程 作…

docker简单教程(三)常用操作

docker简单教程(三)常用操作 文章目录 docker简单教程(三)常用操作1:查看所有容器列表:docker ps -a2:查看正在运行的容器列表:docker ps3:运行容器:docker r…

内网渗透之横向移动wmismb密码喷射CrackMapExec

0x01 横向移动之wmi wmi可以通过hash或明文进行验证,不会在系统日志中留下痕迹,使用139端口 复现环境: god.org win2008 dc win2012sql win2008 web 准备: cs上线 win2008web 提权利用ms14-058 抓取hash和明文密码(当获取到其他…

重磅发布,时隔两月——复旦大学MOSS最新0.0.3版本发布

今天中午吃饭的时候无意间看到一则新闻说的就是复旦大学开发的MOSS也就是国产版的类chatGPT对话模型已经发布了最新版本0。0.3,目前公测期间是完全开源免费的,还是可以上手体验一下的。 官方的博客介绍在这里,首页如下所示: 如果…

手机端无线投屏技术及方案推荐

目前主流的无线投屏技术主要又DLNA,Miracast,Airplay。 对协议的描述引用知乎作者的文章,原文:AirPlay、Miracast 、DLNA三大协议对比 - 知乎 (zhihu.com) 【DLNA】 DNLA,Digital Living Network Alliance&#xff…

光照的个人推导过程与GL实现

目录 1、前提知识 1.1、GL的绘图过程: 1.2、点积的规则和作用: 1.3、normalize在方向处理上的作用 2、光照控制的理论基础 2.1、自由的实现: 2.2、带有方向性的光——基于dot product的实现 最终效果演示如下: 3、关键代…

可能是史上最详细的MySQL用户和权限原理和实战

前言 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系…

Python - Jupyter - 远程连接Jupyter内核

Python - Jupyter - 远程连接Jupyter内核 前言 假设你有一台高性能服务器(电脑B),并且在上面安装好了Jupyter 现在你想使用你自己常用的电脑(电脑A)编码,但使用电脑B的计算资源。 怎么办呢?…

WPS以普通会员价格升级超级会员

文章目录 一、新会员体系二、基本原理三、升级超级会员1、购买会员时长2、成功通知3、兑换时长 一、新会员体系 4月17日,WPS会员体系全新升级。本次升级,WPS将原“WPS会员”、“稻壳会员”及“超级会员”合并、升级,推出全新的“WPS超级会员…