@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) {// 是否设置了 defaultValue if (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) {// 是否设置了 defaultValue if (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 的问题。

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

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

相关文章

xbox虚拟服务器,Xbox One平台真相:原生Win8/虚拟化运行

Xbox One搭载的是Windows8吗&#xff1f;没错。在Build2014开发者大会上&#xff0c;Frank Savage介绍Xbox One平台未来的开发计划&#xff0c;其中他也揭秘Xbox One平台运行原生Win8系统&#xff0c;那些主机游戏均采用虚拟化技术加载运行。据国外wccftech科技网站透露&#x…

SpringMVC 执行流程解析

SpringMVC 执行流程解析 注&#xff1a;SpringMVC 版本 5.2.15 上面这张图许多人都看过&#xff0c;本文试图从源码的角度带大家分析一下该过程。 1. ContextLoaderListener 首先我们从 ContextLoaderListener 讲起&#xff0c;它继承自 ServletContextListener&#xff0c;用…

无线网服务器mac是什么,电脑MAC和LAN MAC以及WIRELESS MAC是什么关系?

满意答案刘义芳aaa推荐于 2017.12.14采纳率&#xff1a;51% 等级&#xff1a;12已帮助&#xff1a;19753人一楼和二楼的回答都是对的电脑MAC这样说不好理解的&#xff0c;应该说MAC电脑&#xff0c;MAC是Macintosh这个的前三个字母&#xff0c;至于它为什么只用前三个字母做…

mybatis 中 foreach collection的三种用法

foreach的主要用在构建in条件中&#xff0c;它可以在SQL语句中进行迭代一个集合。 foreach元素的属性主要有 item&#xff0c;index&#xff0c;collection&#xff0c;open&#xff0c;separator&#xff0c;close。 item表示集合中每一个元素进行迭代时的别名&#xff0c;i…

@RequestParam详解

RequestParam 主要用于将请求参数区域的数据映射到控制层方法的参数上 首先我们需要知道RequestParam注解主要有哪些参数 value&#xff1a;请求中传入参数的名称&#xff0c;如果不设置后台接口的value值&#xff0c;则会默认为该变量名。比如上图中第一个参数如果不设置va…

Java new关键字和newInstance()方法的区别

1、类的加载方式不同 在执行Class.forName(“a.class.Name”)时&#xff0c;JVM会在classapth中去找对应的类并加载&#xff0c;这时JVM会执行该类的静态代码段。在使用newInstance()方法的时候&#xff0c;必须保证这个类已经加载并且已经连接了&#xff0c;而这可以通过Clas…

springboot 的 RedisTemplate 的 execute 和 executePipelined 功能的区别redis

1.executespring 如下是 springboot 官网原文:springboot Redis provides support for transactions through the multi, exec, and discard commands. These operations are available on RedisTemplate, however RedisTemplate is not guaranteed to execute all operatio…

Error running ‘Tomcat‘: Unable to open debugger port (127.0.0.1:2148): java.net.SocketExceptio

在Web项目运行的时候&#xff0c;IDEA可能会报Error running ‘Tomcat’: Unable to open debugger port (127.0.0.1:2148): java.net.SocketException “socket closed”错误&#xff0c;启动不了Tomcat&#xff0c;在这种时候&#xff0c;网上的解决办法大多都是修改端口的这…

# hive打不开,提示节点过少,进入安全模式~~

hive打不开,提示节点过少&#xff0c;进入安全模式~~ 前段时候给电及除尘&#xff0c;因为经常拆着玩&#xff0c;越熟练越容易大意&#xff0c;一下子把电源排线扯坏了。挼了挼&#xff0c;想凑和着用吧&#xff0c;没想到不知怎么的就会关机&#xff0c;太频繁了。一怒之下取…

Java 枚举(enum) 详解7种常见的用法

JDK1.5引入了新的类型——枚举。在 Java 中它虽然算个“小”功能&#xff0c;却给我的开发带来了“大”方便。 用法一&#xff1a;常量 在JDK1.5 之前&#xff0c;我们定义常量都是&#xff1a; public static fianl… 。现在好了&#xff0c;有了枚举&#xff0c;可以把相关…

sqoop导入hive时间格式问题解决方案

sqoop导入hive时间格式问题解决方案 从mysql导入数据时,发现时间格式有问题,要么是时间后面多一位零,要么要使用时间戳,还能不能好好玩耍了?! 于是,我就逛论坛&#xff0c;找大神&#xff0c;最终无果&#xff0c;也许这个问题过于简单吧&#xff0c;居然没有大牛讨论。想了好…

Java枚举类型(enum)详解

文章目录理解枚举类型枚举的定义枚举实现原理枚举的常见方法Enum抽象类常见方法编译器生成的Values方法与ValueOf方法枚举与Class对象枚举的进阶用法向enum类添加方法与自定义构造函数关于覆盖enum类方法enum类中定义抽象方法enum类与接口枚举与switch枚举与单例模式EnumMapEnu…

hive中导入text文件遇到的坑

今天帮一同学导入一个excel数据&#xff0c;我把excel保存为txt格式&#xff0c;然后建表导入&#xff0c;失败&#xff01;分隔符格式不匹配&#xff0c;无法导入&#xff01;&#xff01;&#xff01;&#xff01;怎么看两边都是\t&#xff0c;怎么不匹配呢&#xff1f; 做为…

开窗函数的意义与用法

开窗函数——排序函数 开窗函数与其他函数的区别是,它不是关联其他表查询,而是在一张表内根据我们的想法自定义的规则分组后对我们组内的数据进行检索和计算。我们自定义的规则所分的组&#xff0c;就如同整张表的一个个小窗口&#xff0c;因此我们开出一个个小窗口并对这些小…

mysql/sqlyog导入txt文件的方法

今天尝试着用sqlyog向mysql中导入数据&#xff0c;用了以下几种&#xff1a; 一、sql载入 格式&#xff1a; LOAD DATA LOCAL INFILE 文件路径 INTO TABLE 表名 FIELDS TERMINATED BY 字段分隔符 LINES TERMINATED BY 行分隔符;直接进去了 代码&#xff1a; LOAD DATA LOCA…

XSS知识总结

XSS基础 跨站脚本&#xff08;英语&#xff1a;Cross-site scripting&#xff0c;通常简称为&#xff1a;XSS&#xff09;是一种网站应用程序的安全漏洞攻击&#xff0c;是代码注入的一种。它允许恶意用户将代码注入到网页上&#xff0c;其他用户在观看网页时就会受到影响。这…

2020有效的邮箱号大全_2020年公众号免费裂变涨粉的3个有效方法,让我一天涨粉6000...

文章来源我的公众号&#xff1a;运营小小喵&#xff08;专注分享新媒体运营干货、写作技巧&#xff09;虽然公众号一直被唱衰&#xff0c;但至今依然还有很多人坚持在做。但现在做公众号最大的问题&#xff0c;除了流量大幅下降&#xff0c;打开率越来越低&#xff0c;更重要的…

Error during job, obtaining debugging information...

今天在插入数据的时候出现错误:Error during job, obtaining debugging information… Ended Job job_1575898012755_0005 with errors Error during job, obtaining debugging information... Examining task ID: task_1575898012755_0005_m_000000 (and more) from job job…

java实现 XSS攻击防护

首先说一下什么是XSS攻击 XSS攻击全称跨站脚本攻击&#xff0c;是为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆&#xff0c; 故将跨站脚本攻击缩写为XSS&#xff0c;XSS是一种在web应用中的计算机安全漏洞&#xff0c;它允许恶意web 用户将代码植入到提供给其它用…

动态分区添加的新字段无法插入数据

我们在使用动态分区的进程中&#xff0c;有时候需要新增字段&#xff0c;新增之后&#xff0c;发现该字段一直为空&#xff0c;无论怎么插入数据&#xff0c;该字段值始终不变。过去的做法就是把表删了&#xff0c;重建带新字段的新表&#xff0c;问题就解决了&#xff0c;今天…