探索SpringMVC-HandlerAdapter之RequestMappingHandlerAdapter-参数解析

news2024/12/25 9:24:50

前言

上回,我们大概讲了下HandlerAdapter。今天带大家来认识一下,我们最常用的RequestMappingHandlerAdapter。不过只能给大家先开个头,讲下参数解析。

RequestMappingHandlerAdapter

在介绍HandlerAdapter时,我们就知道HandlerAdapter屏蔽了DispatcherServlet屏蔽了Handler的调用细节,也知道了@RequestMapping的中间桥梁是HandlerMethod。而这意味着RequestMappingHandlerMappping是负责调用HandlerMethod处理请求逻辑的。于是,我们很自然想到几个重要的问题:

  • 怎么获取方法调用的入参
  • 怎么处理方法调用的返回值
  • 怎么处理方法抛出的异常

深入分析入参解析需求

@RequestMapping的参数来源
常见的几个相信很多同学都是知道:@RequestParam、@RequestBody、@PathVariable、@RequestHeader
还有些可能不太常用的:@SessiontAttribuite、@CookieValue、@MatrixVariable
HttpServletRequest、HttpServletResponse、HttpSession、Locale
这里就不再一一列举了,感兴趣的同学可以通过小标题的链接到官网看看。

  • 问题一:
    从这里可以看到,我们的处理器方法入参五花八门、来源不一而足。因此如果要解析参数,就必须知道这些参数来自于哪里。
  • 问题二:
    在了解问题来自于哪里之后,我们面临的另外一个问题是:参数类型转换。举个例子,@RequestParam的参数来自于地址栏参数,无疑地址栏参数是字符类型。但是如果我们的方法参数是个Date类型,怎么办?又例如,@RequestBody是我们自定义类型,而获取参数体通常是一个Stream。

Spring的答案

  1. 参数解析器:HandlerMethodArgumentResolver
    Spring抽象出来了参数解析器,用于解析不同的参数。也正是因为有各种不同来源的参数,所以Spring实现了多达31个参数解析器,且默认注册的多达25个参数解析器。
    RequestMappingAdapterHandler默认注册的参数解析器
    图示就是RequestMappingAdapterHandler默认注册的参数解析器,而HandlerMethodArgumentResolverComposite是一个特殊的实现。如果从策略模式的角度看,他就是策略选择器。用于寻找可以解析特定参数的参数解析器,并解析参数。

  2. 底层的支持体系
    我们知道不同的参数解析器,负责不同来源的参数解析。但也因此需要不同的技术手段的支撑。其中,下面的两种最为重要:

    • 类型转换体系:ConversionService
      类型转换服务是spring-core中提供的,用于类型转换。而在SpringMVC中,只要是name-value这种类型的参数,则都需要依赖他来进行参数转换。例如,上面提到@RequestParam,将String转为Date。
      注意,他背后是一个体系,涉及到一系列的相关接口,例如:Converter<S,T>。这里不拓展了。

    • 对象属性编辑体系:ConfigurablePropertyAccessor
      ConfigurablePropertyAccessor是spring-beans提供的,用于修改对象属性。最典型的实现就是BeanWrapperImpl。同时他还可以在修改属性值之前进行参数转换。而这个参数类型转换能力,来自于两部分,一个是上面说的ConversionService。另一个是PropertyEditor,通常是我们自定义的。因为大部分的类型转换ConversionService都能做。最后有PropertyHandler通过反射将属性值设置进去。

    • 对象属性绑定: DataBinder
      DataBinder来自于spring-context,主要是利用上述的对象编辑体系来完成属性设置。而其子类WebDataBinder来自于spring-web,利用request作为属性值的来源完成请求入参的属性设置。除此之外,他还能做入参校验。我们的@Valid/@Validated就是他处理的。

    • Http消息转换器:HttpMessageConverter
      Http消息转换器是spring-web的内容,主要有两个作用:

      作用
      将Request中的body参数反序列为目标类型的对象
      将需要返回的响应值序列化为二进制流,通过Response写回响应数据

再回头看类的UML图,我们会发现参数解析器的接口方法中有一个WebDataBinderFactory参数。

  • 为什么需要这个参数?因为某些参数需要通过他来完成属性绑定,同时还需要他的入参校验能力。
  • 为什么是Factory?因为有的参数解析器并不需要WebDataBinder的能力来完成参数解析或者参数校验。例如解析@RequestHeader的Map参数只需要一股脑将所有header封装到Map里面返回即可,没有校验过程。
  • 为什么是作为方法入参,而不是参数解析器的内置属性?因为每个请求的入参对象都是不一样的,如果作为内部属性的话,在执行属性绑定,那得出大问题。

到这里可能有些同学要晕了,一下子WebDataBinder,一下子又是HttpMessageConverter。又说不是所有的参数解析器都需要这些东西。就说咱最常用的两个参数解析器来说吧

参数解析器描述
RequestResponseBodyMethodProcessor从request.body中读取流,并利用HttpMessageConverter转换成目标参数对象。同时还利用WebDataBinder做入参校验
RequestParamMethodArgumentResolver从地址栏获取参数,并利用WebDataBinder做参数类型转换,其底层使用到ConversionService和PropertyEditor。没错,他不支持入参校验。

小结

  1. Spring通过HandlerMethodArgumentResolver进行参数解析。不同的参数解析器,处理不同来源的参数。
  2. 不同的参数解析器依赖的工具不一样。涉及输入输出流的交互时,才需要HttpMessageConverter。而name-value形式的参数解析,例如@HttpHeader、@RequestParam,在从对应的参数来源读取到参数值后,则需要依赖WebDataBinder来实现参数类型转换、属性绑定。

RequestParamMethodArgumentResolver

  • 支持哪些参数
    @RequestParam注解,特殊情况:当Map数据类型时,要求@RequestParam的name属性存在才支持。否则由RequestParamMapMethodArgumentResolver提供支持,用于获取所有的地址栏参数。

  • 怎么解析的
    核心逻辑在他的父类方法AbstractNamedValueMethodArgumentResolver#resolveArgument里面

    1. 将@RequestParam解析到NamedValueInfo(会被缓存)
    2. 从NamedValueInfo获取参数名字
    3. 通过参数名获取参数值
    4. 对参数值进行处理(默认值、是否必须、处理空值)
    5. 通过WebDataBinder进行参数类型转换

    而通过参数名获取参数值,则由对应的实际子类来实现。不用说,对于@RequestParam而言,肯定是RequestParamMethodArgumentResolver从request中获取了。不过,不是简单的request.getParameterValues(paramName),因为参数类型还可能是MultipartFile类型,就需要调用另外的方法获取了。

    此外,值得留意的是,他并不会进行参数校验。

RequestResponseBodyMethodProcessor

  • 支持哪些参数
    不必多说,只要是@RequestBody注解的参数都支持。
  • 怎么解析的
    核心逻辑相对简单:
    1. 通过HttpMessageConverter进行参数的读取转换,无非就是遍历所有的消息转换器去转换。当然,我们的RequestBodyAdvice必然是在这个地方工作的了。而消息转换器最典型的实现json相关的实现了,默认的实现有MappingJackson2HttpMessageConverter、GsonHttpMessageConverter取决于哪个包存在。当然你也可以通过WebMvcConfigurer添加自己的消息转换器。
    2. 通过WebDataBinder进行参数检验。
    3. 如果需要的话,对参数进行包装。针对入参类型:Optional

后记

下次我们就聊开篇谈到的三个问题中的第二个:怎么处理方法调用的返回值。

上一篇:
探索SpringMVC-九大组件之HandlerAdapter
第一篇:
探索SpringMVC-web上下文

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

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

相关文章

自己动手写编译器:从NFA到DFA

上一节我们完成了使用NFA来识别字符串的功能。NFA有个问题就是其状态节点太多&#xff0c;使用起来效率不够好。本节我们介绍一种叫“子集构造”的算法&#xff0c;将拥有多个节点的NFA转化为DFA。在上一节我们描述的epsilon闭包操作可以看到&#xff0c;实际上所有由epsilon边…

「ARM32」MMU和页表的映射过程详解

在ARM32中&#xff0c;MMU主要完成虚拟地址到物理地址的映射&#xff0c;并且能够控制内存的访问权限&#xff0c;而页表是实现上述功能的主要手段。页表又分为一级页表、二级页表&#xff0c;在ARM64中甚至还有三级页表。为了便于理解&#xff0c;本章主要讲述一级页表完成段映…

centos7 安装Mysql详细教程

centos7 弃用了Mysql&#xff0c;默认安装了MariaDB&#xff0c;MariaDB是Mysql一个分支&#xff0c;所以要想在centos上安装Mysql&#xff0c;需要先进行卸载MariaDB&#xff0c;避免冲突 本次教程所用环境&#xff1a; 腾讯云服务器&#xff1a;centos7Mysql5.7 1. 卸载Ma…

dubbo源码实践-protocol层-invoker理解

1概述Invoker官方解释&#xff1a;Invoker 是实体域&#xff0c;它是 Dubbo 的核心模型&#xff0c;其它模型都向它靠扰&#xff0c;或转换成它&#xff0c;它代表一个可执行体&#xff0c;可向它发起 invoke 调用&#xff0c;它有可能是一个本地的实现&#xff0c;也可能是一个…

Python 机器学习最常打交道的 27 款工具包

为了大家能够对人工智能常用的 Python 库有一个初步的了解&#xff0c;以选择能够满足自己需求的库进行学习&#xff0c;对目前较为常见的人工智能库进行简要全面的介绍。 1、Numpy NumPy(Numerical Python)是 Python的一个扩展程序库&#xff0c;支持大量的维度数组与矩阵运算…

Maix Bit(K210)保姆级入门上手教程

Maix Bit&#xff08;K210&#xff09;快速上手 这是K210快速上手系列文章&#xff0c;主要内容是&#xff0c;设备连接&#xff0c;环境准备&#xff0c;运行第一个程序 阅读文章前提&#xff1a;python基础,K210是使用Micropython脚本语法的&#xff0c;因此需要一些python…

RocketMQ5.0.0部署与实例

一、Idea调试1.相关配置文件在E:\rocketmq创建conf、logs、store三个文件夹。从RocketMQ distribution部署目录中将broker.conf、logback_namesrv.xml、logback_broker.xml文件复制到conf目录。如下图所示。其中logback_namesrv.xml、logback_broker.xml分别是NameServer、Brok…

纯C语言实现动态爱心(详解,初学者也能看懂)

文章目录✍动态爱心实现&#x1f496;一段小故事&#xff1a;爱心函数的由来&#x1f388; 创建动态爱心的准备&#xff08;非小白可以跳过&#xff09;1.爱心字符2.对easyx库里面的基础函数的认识①initgraph函数②settextcolor、settextstyle、setbkmode、outtextxy四种函数③…

PostgresSQL数据库的使用

PostgresSQL数据库的使用 下载安装 数据类型 使用指导 数据库操作 连接控制台 psql -h <实例连接地址> -U <用户名> -p <端口号>参数描述实例连接地址RDS PostgreSQL实例的连接地址&#xff0c;本机可用localhost或者127.0.0.1用户名创建的RDS Postgre…

ES语法扩展

剩余参数 剩余参数本质 // 剩余参数的本质const add(x,y,...args)>{console.log(x,y,args);}add();add(1);add(1,2);add(1,2,3,4,5); 剩余参数的注意事项 箭头函数的参数部分即使只有一个剩余参数&#xff0c;也不能省略圆括号使用剩余参数替代arguments获取实际参数剩余…

4.Isaac Jetson Nano 入门

Isaac Jetson Nano 入门 本节介绍如何在 Jetson Nano 设备上运行 Isaac SDK 示例应用程序。 有关如何开始使用 Nano 的一般说明&#xff0c;请参阅 Jetson Nano 开发工具包入门。 文章目录Isaac Jetson Nano 入门获取 IP 地址在 Jetson Nano 上运行示例应用程序PingOpenCV 边缘…

Pytorch CIFAR10图像分类 EfficientNet v1篇

Pytorch CIFAR10图像分类 EfficientNet v1篇 文章目录Pytorch CIFAR10图像分类 EfficientNet v1篇4. 定义网络&#xff08;EfficientNet&#xff09;EfficientNet介绍EfficientNet性能比较EfficientNet的baselineEfficientNet模型混合缩放方法其他版本的EfficientNet(B1-B7)判断…

错题 3jxn (8253复杂)

A 指示型指令 C 比如 ,跟C语言的return 一样&#xff0c;可以由多条&#xff0c;但是返回的位置都是一个地方 JN NEXT RET NEXT: RET A 可以重复 EQU不可以重复 C 中断向量&#xff1a;中断服务程序的入口地址 向量中断&#xff1a;识别中断你的方法 接口 编程题&#xff…

Redis关键知识点总结

Reference: http://redis.cn用处缓存数据库分布式锁&#xff08;Redission的redlock&#xff0c;自定义的lock等&#xff09;过滤器&#xff08;布隆过滤器/增强的带计数的布隆过滤器/布谷鸟过滤器等&#xff09;大规模的计算辅助&#xff08;bitmap&#xff09;消息订阅/监听 …

PyQt5入门学习(一)【小白入门系列】

PyQt5入门学习 介绍&#xff1a;PyQt5是Python较好的图形库&#xff0c;与C的Qt不同的是PyQt5封装得较为简单&#xff0c;上手也更加的方便。下面话不多说&#xff0c;开始学习PyQt5吧&#xff01; 安装过程 安装方法有两种&#xff0c;一种是下载PyQt5最新源码进行编译安装…

初识Kafka

1.1 定义 Kafka传统定义: Kafka是一个分布式的基于发布/订阅模式的消息队列(MessageQueue&#xff09;&#xff0c;主要应用于大数据实时处理领域。 发布/订阅: 消息的发布者不会将消息直接发送给特定的订阅者&#xff0c;而是将发布的消息分为不同的类别&#xff0c;订阅者只…

[数据结构基础]二叉树——堆的概念、结构、接口函数及经典的Topk问题和堆排序问题

目录 一. 堆的概念及结构 1.1 堆的概念 1.2 堆的结构及在内存中的存储 二. 堆的主要接口函数 2.1 堆初始化函数HeapInit 2.2 堆销毁函数HeapDestroy 2.3 向堆中插入数据函数HeapPush&#xff08;以小堆为例&#xff09; 2.4 删除堆根节点数据函数HeapPop&#xff08;小…

C++ 夺冠!成为 TIOBE 2022 年度编程语言

目录&#xff1a;C夺冠—成为TIOBE2022年度编程语言一、前言二、C 摘得桂冠三、Top 10 编程语言 TIOBE 指数走势&#xff08;2002-2023&#xff09;四、历史排名&#xff08;1987-2023&#xff09;五、编程语言“名人榜”&#xff08;2003-2022&#xff09;六、说明一、前言 2…

vitepress(三):自动生成目录

上一节我们将自己的网站发布到了git pages上&#xff0c;但是现在我们需要每次更新一篇文章就重写一次目录&#xff0c;操作上十分的繁琐和不方便&#xff0c;所以我们需要一个方法去自动生成我们的侧边栏结构&#xff0c;方便我们每次只需要更新我们的项目即可。这里我们要知道…

【每日一题】【LeetCode】【第六天】【Python实现】加一

加一的解决之路 题目描述 测试案例&#xff08;部分&#xff09; 第一次 1这个很好理解&#xff0c;唯一的难点就是个位1导致的进位的问题&#xff0c;可能会只会导致十位1&#xff0c;也有像8999这样产生多次进位的情况。 为了解决进位问题&#xff0c;自己想到了第三天学…