注: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;
@Nullable
private 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 的问题。