SpringBoot2:请求处理原理分析-接口参数解析原理(argumentResolvers)
一、知识回顾
我们知道,接口的参数,一般都要配上注解来一起使用。
不同的参数注解,决定了传参的方式不同。
为什么会这样?
如果让你设计接口参数解析,你会怎么做?
首先,我们知道方法参数是形参。具体的实参是request中带来的。
那么springboot底层是如何将path中的实参与接口的形参对应上的?
二、源码解读
首先,我们知道接口肯定是归DispatcherServlet类来管理的,所以,我们直接进入这个类
前面的章节,我们已经知道,接口入口方法是:org.springframework.web.servlet.DispatcherServlet#doDispatch
所以,我们进入这个方法进行断点跟踪并分析原理。
DispatcherServlet#doDispatch方法
1、找到对应request的Handler
这里的原理解释在:
SpringBoot2:请求处理原理分析-请求Path与接口的映射关系(HandlerMapping)

2、找Handler的适配器

mappedHandler.getHandler()
这个已经拿到具体的controller了,返回类型是HandlerMehod类型
那么,为什么不直接就反射调用了?还弄个适配器干啥?

我们进入getHandlerAdapter方法

这里的适配器,有4种

我们在看看适配器结构
它是一个接口,有三个方法。

它有以下几个实现类

这里我们会发现,实现类有6个,为什么,handlerAdapters变量只有4个了?
我们继续看源码,会发现是通过DispatcherServlet.properties配置好的。


D:/app/maven/repository/org/springframework/spring-webmvc/5.2.9.RELEASE/spring-webmvc-5.2.9.RELEASE.jar!/org/springframework/web/servlet/DispatcherServlet.properties

继续往下看
通过getHandlerAdapter方法,我们可以看出,Adapters是通过supports方法来确定具体哪个适配器来处理。
supports的具体逻辑就不看了,因为,每个实现的adapter判断逻辑不通。
也就是说,getHandlerAdapter方法循环遍历四个adapters,通过adapter的supports方法,找到了handler对应的HandlerAdapter类。
3、通过适配器处理controller接口参数
我们常用的是@RequestMapping类型的接口注解。
所以这里,我主要解读一下org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

这里,我们可以看出ha.handle是具体的哪个adapter来实现的,从而进入对应的实现类里进行处理。
我这里肯定是进入RequestMappingHandlerAdapter类中看具体逻辑。
3.1、查看RequestMappingHandlerAdapter适配器处理逻辑
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
这里设置参数解析器和返回值处理器。

有26个参数解析器和15个返回值处理器

26个参数解析器代码位置:
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDefaultArgumentResolvers
注意ServletModelAttributeMethodProcessor解析器,添加了2次
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);// Annotation-based argument resolutionresolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));resolvers.add(new RequestParamMapMethodArgumentResolver());resolvers.add(new PathVariableMethodArgumentResolver());resolvers.add(new PathVariableMapMethodArgumentResolver());resolvers.add(new MatrixVariableMethodArgumentResolver());resolvers.add(new MatrixVariableMapMethodArgumentResolver());resolvers.add(new ServletModelAttributeMethodProcessor(false));resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));resolvers.add(new RequestHeaderMapMethodArgumentResolver());resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new SessionAttributeMethodArgumentResolver());resolvers.add(new RequestAttributeMethodArgumentResolver());// Type-based argument resolutionresolvers.add(new ServletRequestMethodArgumentResolver());resolvers.add(new ServletResponseMethodArgumentResolver());resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RedirectAttributesMethodArgumentResolver());resolvers.add(new ModelMethodProcessor());resolvers.add(new MapMethodProcessor());resolvers.add(new ErrorsMethodArgumentResolver());resolvers.add(new SessionStatusMethodArgumentResolver());resolvers.add(new UriComponentsBuilderMethodArgumentResolver());// Custom argumentsif (getCustomArgumentResolvers() != null) {resolvers.addAll(getCustomArgumentResolvers());}// Catch-allresolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));resolvers.add(new ServletModelAttributeMethodProcessor(true));return resolvers;}

15个返回值处理器代码位置
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDefaultReturnValueHandlers
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);// Single-purpose return value typeshandlers.add(new ModelAndViewMethodReturnValueHandler());handlers.add(new ModelMethodProcessor());handlers.add(new ViewMethodReturnValueHandler());handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));handlers.add(new StreamingResponseBodyReturnValueHandler());handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice));handlers.add(new HttpHeadersReturnValueHandler());handlers.add(new CallableMethodReturnValueHandler());handlers.add(new DeferredResultMethodReturnValueHandler());handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));// Annotation-based return value typeshandlers.add(new ModelAttributeMethodProcessor(false));handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice));// Multi-purpose return value typeshandlers.add(new ViewNameMethodReturnValueHandler());handlers.add(new MapMethodProcessor());// Custom return value typesif (getCustomReturnValueHandlers() != null) {handlers.addAll(getCustomReturnValueHandlers());}// Catch-allif (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));}else {handlers.add(new ModelAttributeMethodProcessor(true));}return handlers;}
执行并处理

org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
通过这个名字,我们也可以看出,这里invokeForRequest就是已经处理完请求了。

进入invokeForRequest
关键代码找到了,getMethodArgumentValues获取方法参数值。

org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}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;}
首先,我们看下参数解析器 规范
有两个方法
supportsParameter用来判断是否支持解析
resolveArgument用来进行具体的解析操作

这里用了设计模式中的组合模式
HandlerMethodArgumentResolverComposite实现了HandlerMethodArgumentResolver接口
然后,在该类中循环遍历,是否有解析器可以处理当前参数,如果有,具体怎么解析。
关键代码行
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
通过断点,我们可以看出,此时args[i]被赋值了。
继续进入this.resolvers.resolveArgument查看逻辑


99行,获取形参名,108行才是给形参赋值。
所以,在108行之前,都是解析接口方法形参。
我们在看下resolveName方法,可以看到,这个方法有很多具体的实现类。
我这里以PathVariable为例子。

org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver#resolveName

可以看出,给形参名的值,是从request中获取到的。这样,形参和实参就对应上了。
那么,这里的request.getAttribute是如何处理好的,这里没有说明。
网上说是通过org.springframework.web.util.UrlPathHelper类实现的。
具体有兴趣的同学自己去研究一下。
那么,到这里,源码逻辑就差不多结束了。
三、逻辑梳理
首先,请求接口进入DispatcherServlet#doDispatch方法
先从handlerMapping中获取具体的handler,即找到具体是哪个controller来处理请求。
而handlerMapping类型默认配置了5种。
找到对应的handler后,在找到对应的handlerAdapter
handlerAdapters适配器类型默认配置了4种。
再然后,通过具体的handlerAdapter来解析方法参数
而解析方法参数,用到了参数解析器argumentResolvers
这里,参数解析器,配置了26种。
并且用了组合模式+缓存来优化性能。
遍历循环参数解析器,找到对应参数的解析器后
在通过接口方法的形参信息,如参数类型,参数名称,参数注解等。
进行具体的参数解析,并从request中获取实参值,赋值给形参,完成参数解析。
相关文章:
SpringBoot2:请求处理原理分析-接口参数解析原理(argumentResolvers)
一、知识回顾 我们知道,接口的参数,一般都要配上注解来一起使用。 不同的参数注解,决定了传参的方式不同。 为什么会这样? 如果让你设计接口参数解析,你会怎么做? 首先,我们知道方法参数是形…...
java实现文本相似度计算
需求 **文本推荐:**有多个文本字符串,如何设计一个简单的统计方法(从词频的角度设计),来计算出多个文本字符串两两之间的相似度,并输出大于指定相似度阈值的文本 分析理解 使用Java实现文本相似度计算的…...
基于无人机边沿相关 ------- IBUS、SBUS协议和PPM信号
文章目录 一、IBUS协议二、SBUS协议三、PPM信号 一、IBUS协议 IBUS(Intelligent Bus)是一种用于电子设备之间通信的协议,采用串行通信方式,允许多设备通过单一数据线通信,较低延迟,支持多主机和从机结构&a…...
django学习入门系列之第十点《A 案例: 员工管理系统4》
文章目录 6 部门管理(原始方式)6.6 添加界面的导入(数据库)6.7 删除按键的应用6.8 编辑按键的应用6.81 传值的另一种方式 6.9 提交按键的应用 往期回顾 6 部门管理(原始方式) 6.6 添加界面的导入ÿ…...
【2024】Math-Shepherd:无需人工注释即可逐步验证和强化法学硕士。
搜索词: Math-shepherd: Verify and reinforce llms step-by-step without human annotations P Wang, L Li, Z Shao, R Xu, D Dai, Y Li, D Chen, Y Wu, Z Sui Proceedings of the 62nd Annual Meeting of the Association for …, 2024•aclanthology.org 摘要…...
[苍穹外卖]-08微信支付详解
地址簿管理 分析需求: 查询地址列表/新增地址/修改地址/删除地址/设置默认地址/查询默认地址 接口设计 新增地址接口 查询用户所有的地址接口 查询默认地址接口 根据id修改地址接口 根据id删除地址接口 根据id查询地址接口 设置默认地址接口 数据库设计: 收货地址簿(address_…...
教你五句在酒桌上和领导说的话语
1、今天很荣幸能和领导一起吃饭,我敬领导一杯希望领导工作顺利身体健康!生意兴隆!2、我敬领导一杯感谢领导平时对我的关照先干为敬!3、谢谢领导这次给我这个机会我一定会好好把握的请领导放心我一定会好好工作绝对不辜负领导对我的期望4.领导能来这里我们感到非常骄…...
景联文科技:专业图像采集服务,助力智能图像分析
景联文科技是专业数据服务公司,致力于为人工智能企业提供从数据采集、清洗到标注的全流程解决方案。协助客户解决AI开发过程中数据处理环节的关键问题,助力企业实现智能化转型。 1.多样化的图像采集服务 景联文科技提供多样化的图像采集服务,…...
QT QTcpSocket作为客户端
前言 QTcpSocket是Qt提供的关于TCP网络通信的类。QTcpSocket是一个异步的类,能够非阻塞式发送和接收数据。QTcpSocket内部封装了网络通信相关细节,对外提供便利的接口去帮助开发人员实现简历连接、断开连接、数据收发。 主要内容 基本使用方式 项目文…...
【系统架构设计师-2023年】综合知识-答案及详解
更多内容请见: 备考系统架构设计师-核心总结索引 文章目录 【第1~2题】【第3题】【第4~5题】【第6题】【第7题】【第8题】【第9题】【第10~11题】【第12题】【第13题】【第14题】【第15题】【第16题】【第17题】【第18题】【第19题】【第20题】【第21~22题】【第23题】【第24~…...
树莓派3B点灯(1)-- 四种方法
先做个简单一丢丢的吧。。。正好最近工作也要用这个。这次直接给够四种方法,给好给满。分别是Python点,用户空间配置GPIO点,设备树配置内核Leds驱动点,自己写驱动点。 用的板子是树莓派3B,GPIO 26口,蓝光L…...
Android解析XML格式数据
文章目录 Android解析XML格式数据搭建Web服务器Pull解析方式SAX解析方式 Android解析XML格式数据 通常情况下,每个需要访问网络的应用程序都会有一个自己的服务器,我们可以向服务器提交数据,也可以从服务器上获取数据。不过这个时候就出现了…...
数学建模笔记—— 灰色关联分析[GRA]
数学建模笔记—— 灰色关联分析[GRA] 灰色关联分析(GRA)1. 相关概念1.1 灰色系统1.2 什么是关联分析?1.3 灰色关联分析 2. 关联分析步骤3. 典型例题3.1 关联分析例题3.2 灰色关联综合评价 4. python代码实现4.1 关联分析4.2 灰色关联综合评价 灰色关联分析(GRA) 1.…...
ICM20948 DMP代码详解(13)
接前一篇文章:ICM20948 DMP代码详解(12) 上一回完成了对inv_icm20948_set_chip_to_body_axis_quaternion函数第2步即inv_rotation_to_quaternion函数的解析。回到inv_icm20948_set_chip_to_body_axis_quaternion中来,继续往下进行…...
【论软件需求获取方法及其应用】
摘要 2023 年 3 月,我所在的公司承接了某油企智慧加油站平台的建设工作。该项目旨在帮助加油站提升运营效率、降低运营成本和提高销售额。我在该项目中担任系统架构设计师,负责整个项目的架构设计工作。 本文以该项目为例,详细论述软件需求获…...
使用ESP8266和OLED屏幕实现一个小型电脑性能监控
前言 最近大扫除,发现自己还有几个ESP8266MCU和一个0.96寸的oled小屏幕。又想起最近一直想要买一个屏幕作为性能监控,随机开始自己diy。 硬件: ESP8266 MUColed小屏幕杜邦线可以传输数据的数据线 环境 Windows系统Qt6Arduino Arduino 库…...
Nexpose v6.6.266 for Linux Windows - 漏洞扫描
Nexpose v6.6.266 for Linux & Windows - 漏洞扫描 Rapid7 Vulnerability Management, release Aug 21, 2024 请访问原文链接:https://sysin.org/blog/nexpose-6/,查看最新版。原创作品,转载请保留出处。 作者主页:sysin.o…...
ess6新特性
1、let、const 块级作用域声明变量和常量 2、箭头函数 不能构建函数 不能new 没.prototype属性 没有this指向 this指向是根据上下文的 往上层查找 没有arguments(参数) 3、模板字符串 ${} 字符串中嵌入表达式 4、解构赋值 5、Promise 处理异步操作的标准机制 6、for of 遍历…...
C语言蓝桥杯:语言基础
竞赛常用库函数 最值查询 min_element和max_element在vector(迭代器的使用) nth_element函数的使用 例题lanqiao OJ 497成绩分析 第一种用min_element和max_element函数的写法 第二种用min和max的写法 二分查找 二分查找只能对数组操作 binary_search函数,用于查找…...
axure之变量
一、设置我们的第一个变量 1、点击axure上方设置一个全局变量a 3 2、加入按钮、文本框元件点击按钮文档框展示变量值。 交互选择【单击时】【设置文本】再点击函数。 点击插入变量和函数直接选择刚刚定义的全局变量,也可以直接手动写入函数(注意写入格式。) 这…...
著名学者、顶尖大学教授近期失联
点击下方卡片,关注“CVer”公众号AI/CV重磅干货,第一时间送达点击进入—>【顶会/顶刊】投稿交流群添加微信号:CVer2233,小助手拉你进群!扫描下方二维码,加入CVer学术星球!可以获得最新顶会/顶…...
深入OpenBMC构建系统:Yocto项目与BitBake实战解析(以Romulus平台为例)
深入OpenBMC构建系统:Yocto项目与BitBake实战解析(以Romulus平台为例) 在服务器硬件管理领域,OpenBMC作为开源基板管理控制器固件堆栈,正逐渐成为企业级设备的标准配置。不同于简单的固件烧录,OpenBMC的构建…...
PT-Plugin-Plus:PT站点下载助手安装与使用指南
PT-Plugin-Plus:PT站点下载助手安装与使用指南 【免费下载链接】PT-Plugin-Plus PT 助手 Plus,为 Microsoft Edge、Google Chrome、Firefox 浏览器插件(Web Extensions),主要用于辅助下载 PT 站的种子。 项目地址: h…...
HunyuanVideo-Foley音效生成:支持中文prompt理解的城市环境音效精准生成
HunyuanVideo-Foley音效生成:支持中文prompt理解的城市环境音效精准生成 1. 产品概述 HunyuanVideo-Foley是一款专为视频内容创作设计的AI音效生成工具,能够根据中文文本描述精准生成各类环境音效。本镜像为RTX 4090D 24GB显存显卡深度优化的私有部署版…...
GitHub访问加速终极指南:5分钟告别龟速访问的完整解决方案
GitHub访问加速终极指南:5分钟告别龟速访问的完整解决方案 【免费下载链接】fetch-github-hosts 🌏 同步github的hosts工具,支持多平台的图形化和命令行,内置客户端和服务端两种模式~ | Synchronize GitHub hosts tool, support m…...
语义分割竞赛必备:5种Loss函数组合效果对比(含Dice+Focal Loss调参指南)
语义分割竞赛进阶:5种损失函数组合实战评测与调参策略 在Kaggle等数据竞赛中,语义分割任务的性能提升往往取决于损失函数的巧妙选择与组合。不同于常规分类任务,多类别像素级预测需要处理极端类别不平衡、边界模糊等独特挑战。本文将深入剖析…...
NPU vs GPU:为什么你的AI项目需要专用神经网络处理器?
NPU vs GPU:为什么你的AI项目需要专用神经网络处理器? 当你在深夜调试一个实时人脸识别模型时,GPU风扇的轰鸣声是否让你担心电费账单?当部署在边缘设备的图像分类服务因为响应延迟被客户投诉时,是否考虑过硬件选型可能…...
效率飙升:用快马平台一键生成项目模板,告别重复的vscode环境配置
作为一个经常需要在新设备上配置开发环境的前端开发者,我深刻体会到重复搭建项目的痛苦。每次换电脑或者开新项目,都要从头安装VSCode插件、配置构建工具、集成UI库……这些琐碎工作至少会浪费半小时。最近发现InsCode(快马)平台能完美解决这个问题&…...
Kettle错误处理实战:如何用表输出步骤捕获并存储ETL过程中的异常数据
Kettle错误处理实战:如何用表输出步骤捕获并存储ETL过程中的异常数据 在数据仓库和ETL(Extract, Transform, Load)流程中,错误处理是确保数据质量的关键环节。Kettle(现称Pentaho Data Integration)作为一款…...
别再混淆了!深入对比Vivado中AXI DMA IP核与PS端DMA控制器的角色与分工
深入解析Vivado中AXI DMA与PS端DMA控制器的协同设计 在Zynq/MPSoC平台的软硬件协同开发中,数据搬运效率往往成为系统性能的瓶颈。许多开发者虽然能够熟练使用Vivado中的AXI DMA IP核完成基本数据传输,却对PL端AXI DMA与PS端DMA控制器之间的分工协作机制存…...
