@RequestParam 注解原理
@RequestParam 注解原理
注:SpringMVC 版本 5.2.15
介绍
@RequestParam 注解用于绑定请求参数。它的具体内容如下:
// 该注解作用的方法形参
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {/*** 要绑定的参数名*/@AliasFor("name")String value() default "";/*** 要绑定的参数名*/@AliasFor("value")String name() default "";/*** 是否必须提供参数。默认为 true* 当为 true 时,不提供参数将抛出异常*/boolean required() default true;/*** 没有提供参数时,以该值作为参数值。* 提供了参数将会使用提供的参数值* 设置了该值的话,会隐式的设置 required 为 false*/String defaultValue() default ValueConstants.DEFAULT_NONE;
}
接下来我们看下 SpringMVC 的源码中是怎样用 @RequestParam 注解的。具体为何调用了以下方法可以看我的另一篇文章。[SpringMVC 执行流程解析]
源码分析
AbstractNamedValueMethodArgumentResolver # resolveArgument
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 创建一个 NamedValueInfo 对象NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);MethodParameter nestedParameter = parameter.nestedIfOptional();// 解析参数名Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);if (resolvedName == null) {throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");}// 获取参数值Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);// 没有获取到参数值if (arg == null) {// 是否设置了 defaultValueif (namedValueInfo.defaultValue != null) {arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}// required 属性是否为 true,为 true 则会抛出异常else if (namedValueInfo.required && !nestedParameter.isOptional()) {handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}// 未获取到参数值// 如果参数类型是 boolean 类型的,则设置参数值为 false// 如果参数类型是其他基本数据类型(原生类型,非包装类型),则抛出异常arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());}else if ("".equals(arg) && namedValueInfo.defaultValue != null) {arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}...return arg;
}
这里创建了一个 NamedValueInfo 对象,我们来看下这个类。
/*** Represents the information about a named value, including name, whether it's required and a default value.*/
protected static class NamedValueInfo {private final String name;private final boolean required;@Nullableprivate final String defaultValue;public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {this.name = name;this.required = required;this.defaultValue = defaultValue;}
}
看它的属性,是不是和 @RequestParam 注解中的属性一样,它就是用来包装 @RequestParam 注解中的属性的。接下来我们看一下它的创建过程。
AbstractNamedValueMethodArgumentResolver # getNamedValueInfo
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {// 从缓存中获取NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);if (namedValueInfo == null) {// 创建一个 NamedValueInfo 对象namedValueInfo = createNamedValueInfo(parameter);// 基于上面的 NamedValueInfo 对象// 创建一个新的 NamedValueInfo 对象namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);// 加入缓存this.namedValueInfoCache.put(parameter, namedValueInfo);}return namedValueInfo;
}
该方法首先会尝试从缓存中获取 NamedValueInfo 对象,缓存中没有的话就调用 createNamedValueInfo() 方法去创建一个 NamedValueInfo 对象,然后基于刚才创建的对象再调用 updateNamedValueInfo() 方法创建一个新的 NamedValueInfo 对象,最后加入缓存中。
RequestParamMethodArgumentResolver # createNamedValueInfo
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {// 获取参数上的 @RequestParam 注解RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);// 加了 @RequestParam 注解使用有参构造器创建一个 RequestParamNamedValueInfo 对象// 没有加 @RequestParam 注解使用无参构造器创建一个 RequestParamNamedValueInfo 对象return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}
public RequestParamNamedValueInfo(RequestParam annotation) {super(annotation.name(), annotation.required(), annotation.defaultValue());
}
public RequestParamNamedValueInfo() {super("", false, ValueConstants.DEFAULT_NONE);
}
该方法中会去尝试获取参数中的 @RequestParam 注解,并将它包装成 RequestParamNamedValueInfo 对象
AbstractNamedValueMethodArgumentResolver # updateNamedValueInfo
private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {// 获取参数名。// @RequestParam 注解中的 value 属性值String name = info.name;// 没有获取到参数名// 没有加 @RequestParam 注解或没有设置 value 属性值if (info.name.isEmpty()) {// 去获取参数名name = parameter.getParameterName();if (name == null) {throw new IllegalArgumentException("Name for argument of type [" + parameter.getNestedParameterType().getName() +"] not specified, and parameter name information not found in class file either.");}}// 解决 @RequestParam 注解的 defaultValue 的值不能设置为 null 的问题String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);return new NamedValueInfo(name, info.required, defaultValue);
}
该方法中首先会获取 @RequestParam 注解中的 value 属性值作为参数名,如果参数上没有加 @RequestParam 注解或没有设置 value 属性值,那么会调用 getParameterName() 方法去获取参数名。而且通过 ValueConstants.DEFAULT_NONE 这个值解决了 @RequestParam 注解的 defaultValue 的值不能设置为 null 的问题。
MethodParameter # getParameterName
public String getParameterName() {if (this.parameterIndex < 0) {return null;}// 参数名发现器ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;if (discoverer != null) {String[] parameterNames = null;// 非构造方法if (this.executable instanceof Method) {// 获取参数名parameterNames = discoverer.getParameterNames((Method) this.executable);}// 构造方法else if (this.executable instanceof Constructor) {parameterNames = discoverer.getParameterNames((Constructor<?>) this.executable);}if (parameterNames != null) {this.parameterName = parameterNames[this.parameterIndex];}this.parameterNameDiscoverer = null;}return this.parameterName;
}
该方法中会去判断调用的方法是构造方法还是非构造方法,然后调用 getParameterNames() 方法去获取参数名。
LocalVariableTableParameterNameDiscoverer # getParameterNames
public String[] getParameterNames(Method method) {// 获取桥接方法的原始方法Method originalMethod = BridgeMethodResolver.findBridgedMethod(method);// 获取参数名return doGetParameterNames(originalMethod);
}
该方法首先会判断 method 是否是一个桥接方法,如果是桥接方法则会去获取它的原始方法。然后调用 doGetParameterNames() 方法。
LocalVariableTableParameterNameDiscoverer # doGetParameterNames
private String[] doGetParameterNames(Executable executable) {Class<?> declaringClass = executable.getDeclaringClass();// 先从缓存中获取参数名,获取不到调用 inspectClass() 方法Map<Executable, String[]> map = this.parameterNamesCache.computeIfAbsent(declaringClass, this::inspectClass);return (map != NO_DEBUG_INFO_MAP ? map.get(executable) : null);
}
该方法首先回从缓存中获取参数名,获取不到则调用 inspectClass() 方法去获取。
LocalVariableTableParameterNameDiscoverer # inspectClass
private Map<Executable, String[]> inspectClass(Class<?> clazz) {// 加载字节码文件InputStream is = clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz));if (is == null) {...return NO_DEBUG_INFO_MAP;}try {// 通过 ASM 框架技术从字节码文件中获取参数名ClassReader classReader = new ClassReader(is);Map<Executable, String[]> map = new ConcurrentHashMap<>(32);classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0);return map;}...return NO_DEBUG_INFO_MAP;
}
该方法中会通过 ASM 框架技术从字节码文件中获取参数名。
执行完这些就已经可以获取到参数名了。
再回到最开始的方法
AbstractNamedValueMethodArgumentResolver # resolveArgument
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 创建一个 NamedValueInfo 对象NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);MethodParameter nestedParameter = parameter.nestedIfOptional();// 解析参数名Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);if (resolvedName == null) {throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");}// 获取参数值Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);// 没有获取到参数值if (arg == null) {// 是否设置了 defaultValueif (namedValueInfo.defaultValue != null) {arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}// required 属性是否为 true,为 true 则会抛出异常else if (namedValueInfo.required && !nestedParameter.isOptional()) {handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}// 未获取到参数值// 如果参数类型是 boolean 类型的,则设置参数值为 false// 如果参数类型是其他基本数据类型(原生类型,非包装类型),则抛出异常arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());}else if ("".equals(arg) && namedValueInfo.defaultValue != null) {arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}...return arg;
}
获取参数名后就通过 request.getParameter() 方法去获取参数值,然后对 @RequestParam 中的属性进行一 一判断,内容比较简单,留给大家自己看了。
总结
- @RequestParam 中的 value 属性值即为参数名,若没有给定 value 属性值或没有加 @RequestParam 注解,则通过 ASM 框架的技术去获取参数名。
- @RequestParam 中的 required 属性值为 true 时,则必须提供参数,否则将抛出异常。为 false 时,可以不提供参数
- 当没有提供参数或参数值为空时,@RequestParam 中的 defaultValue 属性值将会作为默认的参数值。提供默认值会隐式地将 required 设置为false
- 将 defaultValue 值设置为 ValueConstants.DEFAULT_NONE 可以解决 defaultValue 不能设置为 null 的问题。
@RequestParam 注解原理相关推荐
- RequestParam注解在required设置为true时失效
在请求参数为http://url?param时,RequestParam注解标记param为required时失败. 1.原因分析 注解解析时序图为 当请求中只有请求参数,没有对其赋值时,会进入下面逻 ...
- @RequestParam注解详解
@RequestParam是传递参数的. @RequestParam用于将请求参数区数据映射到功能处理方法的参数上. public String queryUserName(@RequestParam ...
- @RequestParam 注解的使用——Spring系列知识学习笔记
一. 前言 在SpringMVC后台进行获取数据,一般是两种. request.getParameter("参数名") 用@RequestParam注解获取 下面讲解用法. 二. ...
- springMVC笔记系列——RequestParam注解
摘要: 前面的文章介绍过注解@PathVariable,它能够为Rest风格的URL用占位符的方式传递一个参数,但是这个参数并不是真正意义上的请求参数.请求参数怎么处理是本文的主要内容. 前面的文章介 ...
- 使用@RequestParam注解和泛型遇到的问题
@RequestParam注解的作用是给传入的参数起一个别名,但是当参数中含有泛型的时候,该注解无法识别泛型 去掉@RequestParam注解之后
- requestparam的作用_关于@RequestMapping和@RequestParam注解(四)
通过配置@RequestMapping,可以绑定请求路径与处理请求的方法,例如: @RequestMapping("login.com") public String showLo ...
- SpringMVC框架----RequestParam注解和RequestBody注解
1.RequestParam注解 作用:把请求中指定名称的参数给控制器中的形参赋值. 如果表单提交一个属性username,后台想接收到这个数据,必须在方法中加一个参数叫username,如果名字写的 ...
- @RequestParam注解四个属性字段说明
当前spring-web依赖版本为: <!-- https://mvnrepository.com/artifact/org.springframework/spring-web --> ...
- Spring Boot 注解原理
Spring Boot 注解原理 首先,先看SpringBoot的主配置类: @SpringBootApplication public class StartEurekaApplication {p ...
最新文章
- Linux LNMP环境的搭建 详细步骤
- html取 输入框中的值,jquery获取input输入框中的值
- tf-idf:信息检索
- RocketMQ-初体验RocketMQ(03)_RocketMQ多机集群部署
- sum 去重_Excel函数,用到什么学什么!多条件求和神器之SUMIFS和去重
- Python 的变量作用域和 LEGB 原则
- Openshift 4.4 静态 IP 离线安装系列:初始安装
- c语言 更新学生信息,求学生信息管理系统C语言版
- Spring - 理解BeanPostProcessor
- face_recognition初始
- 7-设计模式之行为模式(模板方法、策略、命令、责任链)
- Leetcode刷题95. 不同的二叉搜索树 II
- 统计学习方法详解之第十三章 无监督学习概论
- PS批量处理批量裁减不同尺寸图片教程(超详细教程 非常实用)-photoshop
- php 实现心芯图案,利用php输出不同的心形图案,php心形图案
- 2.2磁盘IO网络IO工作机制
- 窗体泄漏错误has leaked window android.widget
- 金融直播方兴未艾,理财直播探索新道路
- 【网页图标】favicon.ico文件的设置
- 【葡萄城报表案例分享】项目施工进度报告 – 树形报表
热门文章
- mysql join 循环_关于mysql联表的内嵌循环操作nested loop join中on和where执行顺序问题...
- abap 添加alv上的工具栏的按钮_神器必会!“世界上最好的编辑器Source Insight”...
- 查看文件二进制编码_小白也能学会系列:用python文件读写代码实例!(简单案例)...
- stl swap函数_C ++ STL | vector :: swap()函数与示例
- Java对象都是在堆上分配空间吗?答案竟然是...
- 借力 Docker ,三分钟搞定 MySQL 主从复制!
- LINQ能不能用系列(二)LINQ to SQL 效率比对
- Android 运行时异常 Binary XML file line # : Error inflating class
- ssh框架常见错误与解决方法
- Ubuntu16.04下安装cuda和cudnn的三种方法(亲测全部有效)