@RequestParam 注解原理

@RequestParam 注解原理RequestParam 注解原理介绍源码分析介绍 RequestParam 注解用于绑定请求参数 它的具体内容如下 该注解作用的方法形参 Target ElementType PARAMETER Retention RetentionPol RUNTIME Documentedpu interfaceReq 要绑定的参数名 AliasFor name Stringvalue default

@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 的问题。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/214917.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午3:08
下一篇 2026年3月18日 下午3:09


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号