@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;

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

总结

  1. @RequestParam 中的 value 属性值即为参数名,若没有给定 value 属性值或没有加 @RequestParam 注解,则通过 ASM 框架的技术去获取参数名。
  2. @RequestParam 中的 required 属性值为 true 时,则必须提供参数,否则将抛出异常。为 false 时,可以不提供参数
  3. 当没有提供参数或参数值为空时,@RequestParam 中的 defaultValue 属性值将会作为默认的参数值。提供默认值会隐式地将 required 设置为false
  4. 将 defaultValue 值设置为 ValueConstants.DEFAULT_NONE 可以解决 defaultValue 不能设置为 null 的问题。
  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值