@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 中的屬性進行一 一判斷,內容比較簡單,留給大家自己看了。
總結
總結
以上是生活随笔為你收集整理的@RequestParam 注解原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python3的3D开发-基于blend
- 下一篇: fastjson为什么默认是无序的