@RequestParam注解的使用及源码解析

news2024/10/5 19:54:29

前言

@RequestParam 注解是我们进行JavaEE开发,最常见的几个注解之一,这篇博文我们以案例和源码相结合,帮助大家更好的了解@RequestParam 注解

使用案例

1.获取 URL 上的值

@GetMapping("/simple")
public String simple(@RequestParam(value = "name") String name) {
    return name;
}

2.获取 URL 上的值,如果不存在,使用默认值

@GetMapping("/default")
public String defaultValue(@RequestParam(value = "name", defaultValue = "hello world") String name) {
    return name;
}

3.URL 上存在多个指定的 KEY

@GetMapping("/list")
public String list(@RequestParam(value = "name") List<String> names) {
    return names.toString();
}

 PS : 同样可以使用Set、Collection接收,只要是Collection或其子类都可以

4.使用 Map 接收

4.1 使用接口 Map 接收
@GetMapping("/map")
public String map(@RequestParam Map<String, Object> map) {
    return map.toString();
}

 4.2 使用 MultiValueMap 接收
@GetMapping("/multi_value")
public String map(@RequestParam MultiValueMap<String, Object> map) {
    return map.toString();
}

5.接收文件

5.1 接收单个文件
@GetMapping("/file")
public String multipart(@RequestParam(value = "file") MultipartFile file) {
    return file.getOriginalFilename();
}

5.2 接收多个文件
@GetMapping("/multi_file")
public String multipart(@RequestParam(value = "file") List<MultipartFile> files) {
    return files.stream().map(MultipartFile::getOriginalFilename).collect(Collectors.joining(","));
}

6.其他

6.1 不存在 @RequestParam 注解
@GetMapping("/none")
public String none(String name) {
    return StringUtils.isBlank(name) ? "none" : name;
}

PS : 效果类似上文中的案例2

6.2 Spel表达式
6.2.1 ${}
创建 keys.properties
key=a
引用 keys.properties
@SpringBootApplication
@PropertySource("classpath:keys.properties")
public class BootApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootApplication.class);
    }
}
接口及响应
@GetMapping("/spel1")
public String spel1(@RequestParam(value = "${key}") String name) {
    return name;
}

6.2.1 #{} 
创建 RequestKey
@Component
public class RequestKey {

    private String key = "b";

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}
接口及响应
@GetMapping("/spel2")
public String spel2(@RequestParam(value = "#{requestKey['key']}") String name) {
    return name;
}

源码解析

InvocableHandlerMethod#getMethodArgumentValues

参数的处理分为两个阶段:

  1. 判断当前环境中存在的resolvers,是否支持解析当前参数
  2. 处理参数

判断是否支持解析当前参数

我的环境中存在27个resolvers,通过命名我们大概可以猜测出 RequestParamMethodArgumentResolverRequestParamMapMethodArgumentResolver 是处理 @RequestParam 注解的 resolver

RequestParamMethodArgumentResolver#supportsParameter
@Override
public boolean supportsParameter(MethodParameter parameter) {
    if (parameter.hasParameterAnnotation(RequestParam.class)) {
        // 存在RequestParam注解,返回类型是Map,并且指定了name
        if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
            RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
            return (requestParam != null && StringUtils.hasText(requestParam.name()));
        } else {
            // 存在@RequestParam注解,并且返回类型不是Map
            return true;
        }
    } else {
        // 不存在@RequestPart注解
        if (parameter.hasParameterAnnotation(RequestPart.class)) {
            return false;
        }
        parameter = parameter.nestedIfOptional();
        // 返回类型是 MultipartFile 或者 MultipartFile集合、MultipartFile数组
        // 返回类型是 Part 或者 Part集合、Part数组
        if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
            return true;
            // 如果useDefaultResolution属性为true,即使不存在@RequestParam注解,也可以处理普通类
        } else if (this.useDefaultResolution) {
            return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
        } else {
            return false;
        }
    }
}

通过上述源码,我们得出以下结论:

  • 存在 @RequestParam 注解
    • 返回类型是Map
      • 指定name:支持
      • 未指定name:不支持
    • 返回类型非Map:支持
  • 不存在 @RequestParam 注解
    • 存在 @RequestPart 注解 :不支持
    • 不存在 @RequestPart 注解
      • 参数类型是否是MultipartFile(Part):支持
      • useDefaultResolution属性是否为true,并且参数类型是普通类 : 支持
      • 其他情况 : 不支持

PS : 所以 RequestParamMethodArgumentResolver 也可以处理上文中没有 @RequestParam 注解的情况(案例6.1)。通过下方的截图我们可以发现,存在两个类型都为 RequestParamMethodArgumentResolver 的 resolver ,其中一个的 useDefaultResolution 属性为 true,这个 resolver 就是用来处理没有 @RequestParam 注解,并满足一定条件的传入参数

Spring中定义的普通类
public static boolean isSimpleValueType(Class<?> type) {
    return (Void.class != type && void.class != type &&
            (ClassUtils.isPrimitiveOrWrapper(type) ||
                    Enum.class.isAssignableFrom(type) ||
                    CharSequence.class.isAssignableFrom(type) ||
                    Number.class.isAssignableFrom(type) ||
                    Date.class.isAssignableFrom(type) ||
                    Temporal.class.isAssignableFrom(type) ||
                    URI.class == type ||
                    URL.class == type ||
                    Locale.class == type ||
                    Class.class == type));
}
RequestParamMapMethodArgumentResolver#supportsParameter
@Override
public boolean supportsParameter(MethodParameter parameter) {
    RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
    return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
            !StringUtils.hasText(requestParam.name()));
}

​​​​​​RequestParamMapMethodArgumentResolver 的 supportsParameter 方法比较简单,只能处理满足下面三个条件的参数:

  • 存在 @RequestParam 注解
  • 返回类型是 Map
  • 未指定 value (name)

处理参数

接来下我们将重点分析 RequestParamMethodArgumentResolver 的 resolveArgument 方法,RequestParamMapMethodArgumentResolver 的 resolveArgument 方法大家可以自行阅读,相关源码如下:

大概分为以下五个步骤:

  1. 构建NamedValueInfo对象
  2. 处理Spel表达式
  3. 解析参数
  4. 处理默认值
  5. 类型转换
构建NamedValueInfo对象

创建NamedValueInfo对象

如果存在 @RequestParam 注解,则使用自定义的值,否则就使用默认值,通过上述源码,我们可以得知:

  • @RequestParam 注解的 required 属性的默认值是 true
  • NamedValueInfo 对象的 required 属性的默认值是 false,所以针对案例 6.1,不传相应参数也不会抛出异常
更新NamedValueInfo对象

updateNamedValueInfo 方法主要针对不存在@RequestParam 注解,NamedValueInfo对象的 name 属性值为方法的参数名

处理Spel表达式

主要对Spel表达式进行解析,比如案例 6.2.1 中 ${key} 会被解析成 a ,案例 6.2.2 中 #{requestKey['key']} 会被解析成 b

解析参数
RequestParamMethodArgumentResolver#resolveName

总体分为三个优先级:

  1. HttpServletRequest 类型为 MultipartHttpServletRequest 或 contentType 以 multipart/ 开头,则处理 MultipartFile (Part)类型的传参
  2. HttpServletRequest 类型为 MultipartRequest 则处理 MultipartFile 类型的参数
  3. 将 URL 或 body 中的传参,以 String (String[])返回 (如果存在相应的 convert,则进行类型转换)

PS : 默认情况下,如果 request 的  contentType 以 multipart/ 开头,SpringBoot 会将请求封装成 StandardMultipartHttpServletRequest,它是 MultipartHttpServletRequest 的子类

处理默认值

处理默认值的两种情况

  1. 参数解析结果为 null,@RequestParam 注解的 required 属性为 false,并且设置了默认值
  2. 参数解析结果为空字符串,并且设置了默认值 (尝试以Spel表达式的方式进行解析)
类型转换

SpringBoot 会提前内置很多 convert,当存在一个 convert 可以将当前类型转换为目标类型,则会进行转换。比如案例3中,需要一个将 String 数组转换为 LIst 的 convert,因为该 convert (上图框中的 convert)存在,所以我们可以用 List (当前类型和目标类型与 convert 类型一致或是其子类都可以)去接收参数。

自定义Convert

除了系统内置的 convert,我们也可以自定义 convert,案例演示如下:

创建配置类 WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new Converter<String, Dog>() {
            @Override
            public <U> Converter<String, U> andThen(Converter<? super Dog, ? extends U> after) {
                return Converter.super.andThen(after);
            }

            @Override
            public Dog convert(String source) {
                return new Dog(source);
            }
        });
    }
}
创建实体类 Dog
public class Dog {

    private String name;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}
接口及响应
@GetMapping("/convert")
public String convert(@RequestParam(value = "name") Dog dog) {
    return dog.toString();
}

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

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

相关文章

MySQL自学教程:1. MySQL简介与安装

MySQL简介与安装 一、MySQL简介二、MySQL安装(一)Windows系统上的安装(二)Linux系统上的安装(以Ubuntu为例)(三)Mac OS系统上的安装三、安装后的基本配置四、总结一、MySQL简介 MySQL是一个流行的开源关系型数据库管理系统(RDBMS),广泛应用于各种业务场景,从小型个…

2024肥晨赠书活动第三期:《前端工程化:基于Vue.js 3.0的设计与实践》

文章目录 内容简介作者简介关于《前端工程化&#xff1a;基于Vue.js 3.0的设计与实践》文章目录文章简介《前端工程化&#xff1a;基于Vue.js 3.0的设计与实践》全书速览结束语 内容简介 本书以Vue.js的3.0版本为核心技术栈&#xff0c;围绕“前端工程化”和TypeScript的知识点…

保姆级本地部署Qwen2

重点&#xff1a;Qwen2提供了CPU与GPU两种运行方式 运行成功效果图&#xff1a; 前提说明&#xff1a;如果需要用GPU&#xff0c;那么请在物理机安装ubuntu系统&#xff0c;不然显卡驱动很难安装&#xff0c;不建议新手部署。训练微调模型需要用到GPU。本文仅以ubuntu系统演示…

vue3+ts <script setup lang=“ts“> element-plus的el-date-picker设置默认日期

效果图&#xff08;单个日期&#xff09;&#xff1a; utils.ts&#xff1a; /*** 格式化时间戳* param {number} timestamp 时间戳* param {string} format 格式* returns {string}*/ export const formatTimeStamp (timestamp: number, format: string) > {if (!timesta…

Python魔法参数:深入解析*args和**kwargs的强大用途

目录 引言 基础概念解析 *args:处理位置参数 **kwargs:处理关键字参数 *args和**kwargs的实际应用场景 1. 函数装饰器中使用*args和**kwargs 2. 类构造函数中使用*args和**kwargs 3. API调用中使用**kwargs 与其他参数类型的结合使用 结合默认参数 位置参数与关键…

利用powershell开展网络钓鱼

要确保人们打开我们的恶意文件并执行它们&#xff0c;我们只需让微软努力工作多年来赢得人们的信任&#xff0c;然后将一些危险的宏插入到幻灯片中。 本博文将介绍如何通过屏幕顶部的一个友好的警告提示&#xff0c;在用户启用宏后立即运行您的宏。 首先&#xff0c;我们需要打…

pytest-yaml-sanmu(五):跳过执行和预期失败

除了手动注册标记之外&#xff0c;pytest 还内置了一些标记可直接使用&#xff0c;每种内置标记都会用例带来不同的特殊效果&#xff0c;本文先介绍 3 种。 1. skip skip 标记通常用于忽略暂时无法执行&#xff0c;或不需要执行的用例。 pytest 在执行用例时&#xff0c;如果…

手持小风扇哪个品牌好耐用?手持小风扇品牌排行榜揭晓分享

炎炎夏日&#xff0c;手持小风扇、USB小风扇&#xff0c;成为人手一台的“网红”。这些小风扇造型小巧&#xff0c;可以装进包里&#xff0c;夏日出街或者挤公交地铁都可以拿出来吹一吹。那么这些小风扇性价比高不高呢&#xff1f;真的好用吗&#xff1f;耐用吗&#xff1f;根据…

00. 这里整理了最全的爬虫框架(Java + Python)

目录 1、前言 2、什么是网络爬虫 3、常见的爬虫框架 3.1、java框架 3.1.1、WebMagic 3.1.2、Jsoup 3.1.3、HttpClient 3.1.4、Crawler4j 3.1.5、HtmlUnit 3.1.6、Selenium 3.2、Python框架 3.2.1、Scrapy 3.2.2、BeautifulSoup Requests 3.2.3、Selenium 3.2.4…

web前端——javaScript

目录 一、javaScript概述 1.javaScript历史 2.JavaScript与html,css关系 二、基本语法 ①放在head中 ②放在 body中 ③写在外部的.js文件中 1.变量 2.数据类型 3.算术运算符 4.逻辑运算符 5.赋值运算 6.逻辑运算符 7.条件运算符 8.控制语句 三、函数 1…

简单的text/html无法解析解决记录

简单的text/html无法解析解决记录 1. bug发现 我们所有的服务都是微服务&#xff0c;服务间调用都是使用feign接口进行调用&#xff0c;正常调用都没有问题&#xff0c;但是某一天发现部分从esb服务调用过来到我们本地的服务&#xff0c;本地服务再使用feign接口调用其他微服…

电脑定时重启怎么设置?用这个智能管理电脑定时任务的好帮手!

电脑定时重启怎么设置&#xff1f;用这个智能管理电脑定时任务的好帮手&#xff01;电脑定时重启&#xff0c;这个设置其实很简单&#xff0c;但是很多人都不知道用电脑怎么设置&#xff0c;而且操作也很麻烦&#xff0c;并不好管理&#xff0c;这个时候我们需要一个非常智能的…

模型情景制作-冰镇啤酒

夏日炎炎&#xff0c;当我们在真实世界中开一瓶冰镇啤酒的时候&#xff0c;我们也可以为模型世界中的人物添加一些冰镇啤酒。 下面介绍一种快速酒瓶制造方法&#xff0c;您只需要很少工具&#xff1a; 截取尽量直的流道&#xff08;传说中的板件零件架&#xff09;,将其夹在您的…

adb push 报错 ...error: failed to copy...

一、现象&#xff1a; 原因&#xff1a;没有权限导致的 二、解决方法&#xff1a; adb root adb remount #重新加载文件系统三、再次尝试&#xff1a;adb push xxx.apk /system/app 结果&#xff1a;成功

详细解释Spring事务的传播机制

详细解释Spring事务的传播机制 Spring框架中&#xff0c;事务传播机制是指在一个事务方法调用另一个事务方法时&#xff0c;Spring如何管理这些方法之间的事务边界。Spring提供了七种事务传播行为&#xff0c;以满足不同的业务需求。下面将详细解释每种传播行为及其适用场景&a…

不用翻墙,手把手教你用MAC本地版免费ComfyUI搭建Stable Diffusion工作流,让出图效率起飞

AI绘图如火如荼发展了这么久&#xff0c;从mj到SD webUI,再到时下最热门的Comfy UI。因为显存的问题对Mac用户一直不是很友好&#xff0c;阻碍了大部分设计师上手学习的道路。但是Comflowy解决了这个痛点。这是一款Mac系统可用本地版的sd&#xff0c;一键安装&#xff0c;让苹果…

阿里巴巴找黄金宝箱(IV)

系列文章目录 本人最近再练习算法&#xff0c;所以会发布自己的解题思路&#xff0c;希望大家多指教 文章目录 系列文章目录前言一、题目描述二、输入描述三、输出描述四、java代码五、测试用例 前言 一、题目描述 贫如洗的椎夫阿里巴巴在去砍柴的路上&#xff0c;无意中发现…

基于SpringBoot学生信息管理系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f;感兴趣的可以先收藏起来&#xff0c;还…

操纵系统的特征调度算法

操纵系统的特征 调度算法是操作系统用来决定各个进程/作业在CPU上执行顺序的方法。最常见的调度算法有&#xff1a;FCFS、SJF、HRRN、RR、HPF和MFQ。这集先介绍前三个 先来先服务 FCFS 根据作业到达的先后顺序调度&#xff0c;CPU会一直运行直到作业结束&#xff0c;所以这个…

跌幅高达10.2分!32本Top,Elsevier旗下在检SSCI期刊(2024年6月影响因子更新版)

本周投稿推荐 SSCI • 1区&#xff0c;4.0-5.0&#xff08;无需返修&#xff0c;提交可录&#xff09; EI • 各领域沾边均可&#xff08;2天录用&#xff09; CNKI • 7天录用-检索&#xff08;急录友好&#xff09; SCI&EI • 4区生物医学类&#xff0c;0.1-0.5&…