@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 中的属性进行一 一判断,内容比较简单,留给大家自己看了。

总结

  1. @RequestParam 中的 value 属性值即为参数名,若没有给定 value 属性值或没有加 @RequestParam 注解,则通过 ASM 框架的技术去获取参数名。
  2. @RequestParam 中的 required 属性值为 true 时,则必须提供参数,否则将抛出异常。为 false 时,可以不提供参数
  3. 当没有提供参数或参数值为空时,@RequestParam 中的 defaultValue 属性值将会作为默认的参数值。提供默认值会隐式地将 required 设置为false
  4. 将 defaultValue 值设置为 ValueConstants.DEFAULT_NONE 可以解决 defaultValue 不能设置为 null 的问题。

@RequestParam 注解原理相关推荐

  1. RequestParam注解在required设置为true时失效

    在请求参数为http://url?param时,RequestParam注解标记param为required时失败. 1.原因分析 注解解析时序图为 当请求中只有请求参数,没有对其赋值时,会进入下面逻 ...

  2. @RequestParam注解详解

    @RequestParam是传递参数的. @RequestParam用于将请求参数区数据映射到功能处理方法的参数上. public String queryUserName(@RequestParam ...

  3. @RequestParam 注解的使用——Spring系列知识学习笔记

    一. 前言 在SpringMVC后台进行获取数据,一般是两种. request.getParameter("参数名") 用@RequestParam注解获取 下面讲解用法. 二. ...

  4. springMVC笔记系列——RequestParam注解

    摘要: 前面的文章介绍过注解@PathVariable,它能够为Rest风格的URL用占位符的方式传递一个参数,但是这个参数并不是真正意义上的请求参数.请求参数怎么处理是本文的主要内容. 前面的文章介 ...

  5. 使用@RequestParam注解和泛型遇到的问题

    @RequestParam注解的作用是给传入的参数起一个别名,但是当参数中含有泛型的时候,该注解无法识别泛型 去掉@RequestParam注解之后

  6. requestparam的作用_关于@RequestMapping和@RequestParam注解(四)

    通过配置@RequestMapping,可以绑定请求路径与处理请求的方法,例如: @RequestMapping("login.com") public String showLo ...

  7. SpringMVC框架----RequestParam注解和RequestBody注解

    1.RequestParam注解 作用:把请求中指定名称的参数给控制器中的形参赋值. 如果表单提交一个属性username,后台想接收到这个数据,必须在方法中加一个参数叫username,如果名字写的 ...

  8. @RequestParam注解四个属性字段说明

    当前spring-web依赖版本为: <!-- https://mvnrepository.com/artifact/org.springframework/spring-web --> ...

  9. Spring Boot 注解原理

    Spring Boot 注解原理 首先,先看SpringBoot的主配置类: @SpringBootApplication public class StartEurekaApplication {p ...

最新文章

  1. Linux LNMP环境的搭建 详细步骤
  2. html取 输入框中的值,jquery获取input输入框中的值
  3. tf-idf:信息检索
  4. RocketMQ-初体验RocketMQ(03)_RocketMQ多机集群部署
  5. sum 去重_Excel函数,用到什么学什么!多条件求和神器之SUMIFS和去重
  6. Python 的变量作用域和 LEGB 原则
  7. Openshift 4.4 静态 IP 离线安装系列:初始安装
  8. c语言 更新学生信息,求学生信息管理系统C语言版
  9. Spring - 理解BeanPostProcessor
  10. face_recognition初始
  11. 7-设计模式之行为模式(模板方法、策略、命令、责任链)
  12. Leetcode刷题95. 不同的二叉搜索树 II
  13. 统计学习方法详解之第十三章 无监督学习概论
  14. PS批量处理批量裁减不同尺寸图片教程(超详细教程 非常实用)-photoshop
  15. php 实现心芯图案,利用php输出不同的心形图案,php心形图案
  16. 2.2磁盘IO网络IO工作机制
  17. 窗体泄漏错误has leaked window android.widget
  18. 金融直播方兴未艾,理财直播探索新道路
  19. 【网页图标】favicon.ico文件的设置
  20. 【葡萄城报表案例分享】项目施工进度报告 – 树形报表

热门文章

  1. mysql join 循环_关于mysql联表的内嵌循环操作nested loop join中on和where执行顺序问题...
  2. abap 添加alv上的工具栏的按钮_神器必会!“世界上最好的编辑器Source Insight”...
  3. 查看文件二进制编码_小白也能学会系列:用python文件读写代码实例!(简单案例)...
  4. stl swap函数_C ++ STL | vector :: swap()函数与示例
  5. Java对象都是在堆上分配空间吗?答案竟然是...
  6. 借力 Docker ,三分钟搞定 MySQL 主从复制!
  7. LINQ能不能用系列(二)LINQ to SQL 效率比对
  8. Android 运行时异常 Binary XML file line # : Error inflating class
  9. ssh框架常见错误与解决方法
  10. Ubuntu16.04下安装cuda和cudnn的三种方法(亲测全部有效)