@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 中的属性进行一 一判断,内容比较简单,留给大家自己看了。
总结
- @RequestParam 中的 value 属性值即为参数名,若没有给定 value 属性值或没有加 @RequestParam 注解,则通过 ASM 框架的技术去获取参数名。
- @RequestParam 中的 required 属性值为 true 时,则必须提供参数,否则将抛出异常。为 false 时,可以不提供参数
- 当没有提供参数或参数值为空时,@RequestParam 中的 defaultValue 属性值将会作为默认的参数值。提供默认值会隐式地将 required 设置为false
- 将 defaultValue 值设置为 ValueConstants.DEFAULT_NONE 可以解决 defaultValue 不能设置为 null 的问题。