当前位置: 首页 > news >正文

Spring MVC 参数解析(13)

目录

简介

调用流程

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种:

返回值解析器:

 源码如下:

@Nullableprotected 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支持

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

相关文章:

Spring MVC 参数解析(13)

目录 简介 调用流程 1. 首先,还是需要进行到前端控制器的doDispatch方法,这是我们的调用Spring MVC的核心入口方法 2. 在doDispatch方法内部,我们调用到了HandlerAdapter.handle(*****) 方法 3. 最终,我们会来到 RequestMappi…...

探索 Qt WebEngineWidgets:从底层原理到高级应用与技巧

探索 Qt WebEngineWidgets:从底层原理到高级应用与技巧 (Exploring Qt WebEngineWidgets: From Fundamentals to Advanced Applications and Techniques 一、Qt WebEngineWidgets 模块简介及原理 (Introduction and Principles of Qt WebEngineWidgets Module)1. Qt…...

leetcode160. 相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 图示两个链表在节点 c1 开始相交: 题目数据 保证 整个链式结构中不存在环。 注意,函数返回结果后&…...

核心业务7:放款实现

核心业务7:放款实现 1.放款实现流程 -------------------未完成生成借款人还款计划和投资人回款计划-------------- 2.数据库表 3.前端流程 4.汇付宝流程 5.尚融宝后端流程 -------------------未完成生成借款人还款计划和投资人回款计划-------------- -------------…...

STM32F4系列芯片RTC模块介绍

RTC是“实时时钟”的缩写,它是一种芯片,在计算机等电子产品中广泛应用。RTC提供了实时时钟计时功能和存储时间的能力,即时钟模块,常用于控制和记录时间的应用场合。 RTC的工作原理 RTC主要由时钟电路、电源管理电路、晶振电路、…...

MySQL 在线人数 场景分析

一般在直播或者游戏中经常会统计用户在线人数,主要分为求每个时刻的在线人数和求某个时刻的在线人数两种。 【场景】:某个时刻的在线人数、每个时刻的在线人数 【知识点】:窗口函数、时间函数、sum(tag) over (order by dt,tag desc rows b…...

使用mybatis和dynamic-datasource-spring-boot-starter动态切换数据源操作数据库

记录:415 场景:使用mybatis和dynamic-datasource-spring-boot-starter动态切换数据源操作数据库。 版本:JDK 1.8,Spring Boot 2.6.3,dynamic-datasource-spring-boot-starter-3.3.2,mybatis-3.5.9。 源码:https://github.com/b…...

【日常刷题】迷宫问题

描述 定义一个二维数组 N*M ,如 5 5 数组下所示: int maze[5][5] { 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, }; 它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走…...

【Python童年游戏】满满的回忆杀—那些年玩过的童年游戏你还记得吗?那个才是你的菜?看到第一个我就泪奔了(致我们逝去的青春)

导语 滴一一学生卡🙌 结伴上车的学生仔子们 用笑声打破车厢的沉默 大人眼里的晚高峰 是给放学后快乐😀时光的加时 下车的学生匆匆起身带起 一阵熟悉的栀子香于💓 是关于校园的记忆 开始零零散散地闪现 放学后集合的秘密基地/跟着城…...

C++ | 认识标准库string和vector

本文概要 本篇文章主要介绍C的标准库类型string和vector,文中描述和代码示例很详细,看完即可掌握,感兴趣的小伙伴快来一起学习吧。 🌟🌟🌟个人简介 🌟🌟🌟 ☀️大家好&a…...

JAVA面试宝典: SpringCloud知识点(通俗易懂易背)

1、什么是 Spring Cloud? Spring Cloud 是基于 Spring Boot 的微服务架构开发工具箱,提供了在分布式系统中构建可靠的、弹性的、灵活的应用所需的大多数工具。Spring Cloud 中包含的子项目如下: Spring Cloud Config:配置管理工具…...

es学习笔记

集群环境下数据往哪个节点放? 路由计算:hash(id) %主分片的数量 集群环境下查数据怎么查? 分配控制:访问任何一个节点都能获取数据,随机访问到的这个节点称为协调节点(访问了当前节点,不一定从当前节点…...

SAS学习第9章:卡方检验之适合性检验与独立性检验

卡方检验就是统计样本的实际观测值与理论推断值之间的偏离程度,实际观测值与理论推断值之间的偏离程度就决定卡方值的大小,如果卡方值越大,二者偏差程度越大;反之,二者偏差越小;若两个值完全相等时&#xf…...

马斯克爆料Twitter裁了八成员工;OpenAI CEO:GPT-5根本不存在;小鹏被曝年终奖打0.5折 | AI一周资讯

来源: AI前线 微信号:ai-front 整理 | 凌敏 微软宣布开源 Deep Speed Chat;消息称软银旗下 Arm 启动赴美 IPO;国家网信办出台生成式 AI 管理办法;前理想 AI 芯片一号位骄旸加入三星,负责组建 GPU 团队…… 资 讯 Op…...

ASEMI代理ADG1408YRUZ-REEL7原装ADI车规级ADG1408YRUZ-REEL7

编辑:ll ASEMI代理ADG1408YRUZ-REEL7原装ADI车规级ADG1408YRUZ-REEL7 型号:ADG1408YRUZ-REEL7 品牌:ADI /亚德诺 封装:TSSOP-16 批号:2023 安装类型:表面贴装型 引脚数量:16 类型&#…...

phpstudy本地环境搭建图文教程

作者:Eason_LYC 悲观者预言失败,十言九中。 乐观者创造奇迹,一次即可。 一个人的价值,在于他所拥有的。可以不学无术,但不能一无所有! 技术领域:WEB安全、网络攻防 关注WEB安全、网络攻防。我的…...

【UE 控件蓝图】菜单及功能实现

素材资源连接:百度网盘 请输入提取码 密码:fvcw 效果 步骤 1. 创建蓝图,父类为“HUD” 命名为“MainMenuHUD”并打开 在事件图表中添加如下节点: 2. 创建控件蓝图,命名为“MainMenuWidget” 此时在“MainMenuHUD”的…...

Java 并发编程面试题——Future

目录 1.什么是 Future 模式?Java 中是如何实现的?2.Callable、Future 与 FutureTask 分别是什么?2.1.Callable 接口2.2.Future 接口2.3.FutureTask 类 3.CompletableFuture 类有什么用? 1.什么是 Future 模式?Java 中是…...

SpringBoot 介绍

1.简介 SpringBoot最开始基于Spring4.0设计,是由Pivotal公司提供的框架。 SpringBoot发展史: 2003年Rod Johnson成立Interface公司,产品是SpringFramework2004年,Spring框架开源,公司改名为Spring Source2008年&…...

自动驾驶作业手册

1 总 则 目的为保障港口内自动驾驶车辆安全使用,预防和减少事故,保护人民生命和财产安全,促进港口内业务开展。 含义和范围港口内自动驾驶车辆,是指电脑驾驶车辆,为一种运输动力的无人地面载具,与有人驾驶车辆不同,其具备不需要人类操作即可以感测其环境及导航功能,能…...

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

uniapp中使用aixos 报错

问题&#xff1a; 在uniapp中使用aixos&#xff0c;运行后报如下错误&#xff1a; AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

ip子接口配置及删除

配置永久生效的子接口&#xff0c;2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...