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、加入按钮、文本框元件点击按钮文档框展示变量值。 交互选择【单击时】【设置文本】再点击函数。 点击插入变量和函数直接选择刚刚定义的全局变量,也可以直接手动写入函数(注意写入格式。) 这…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...

基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...