Skip to content

Commit 61c574e

Browse files
committed
Spring MVC: 参数名是从哪里来的?
1 parent ff2254e commit 61c574e

File tree

6 files changed

+561
-4
lines changed

6 files changed

+561
-4
lines changed
52.3 KB
Loading

note/images/debug_info_error.PNG

4.76 KB
Loading

note/images/idea_debug_info.PNG

4.29 KB
Loading
4.95 KB
Loading

note/spring-mvc.md

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,35 @@ protected ModelAndView handleInternal(HttpServletRequest request,
753753

754754
解析由RequestParamMethodArgumentResolver完成。
755755

756-
supportsParameter方法决定了一个解析器可以解析的参数类型,该解析器支持@RequestParam标准的参数或是**简单类型**的参数,具体参见其注释。
756+
supportsParameter方法决定了一个解析器可以解析的参数类型,该解析器支持@RequestParam标准的参数或是**简单类型**的参数,具体参见其注释。为什么此解析器可以同时解析@RequestParam注解和普通参数呢?玄机在于RequestMappingHandlerAdapter方法在初始化参数解析器时其实初始化了**两个RequestMappingHandlerAdapter对象**,getDefaultArgumentResolvers方法相关源码:
757+
758+
```java
759+
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
760+
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
761+
// Catch-all
762+
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
763+
}
764+
```
765+
766+
useDefaultResolution参数用于启动对常规类型参数的解析,这里的常规类型指的又是什么呢?
767+
768+
实际上由BeanUtils.isSimpleProperty方法决定:
769+
770+
```java
771+
public static boolean isSimpleProperty(Class<?> clazz) {
772+
Assert.notNull(clazz, "Class must not be null");
773+
return isSimpleValueType(clazz) || (clazz.isArray() && isSimpleValueType(clazz.getComponentType()));
774+
}
775+
776+
public static boolean isSimpleValueType(Class<?> clazz) {
777+
return (ClassUtils.isPrimitiveOrWrapper(clazz) || clazz.isEnum() ||
778+
CharSequence.class.isAssignableFrom(clazz) ||
779+
Number.class.isAssignableFrom(clazz) ||
780+
Date.class.isAssignableFrom(clazz) ||
781+
URI.class == clazz || URL.class == clazz ||
782+
Locale.class == clazz || Class.class == clazz);
783+
}
784+
```
757785

758786
忽略复杂的调用关系,最核心的实现位于resolveName方法,部分源码:
759787

@@ -772,6 +800,73 @@ protected Object resolveName(String name, MethodParameter parameter, NativeWebRe
772800

773801
name就是方法的参数名,可以看出,参数解析**就是根据参数名去request查找对应属性的过程**,在这里参数类型并没有起什么作用。
774802

803+
###### 参数名是从哪里来的
804+
805+
方法名获取的入口位于RequestParamMethodArgumentResolver的resolveArgument方法:
806+
807+
```java
808+
@Override
809+
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
810+
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
811+
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
812+
}
813+
```
814+
815+
getNamedValueInfo方法最终完成对MethodParameter的getParameterName方法的调用:
816+
817+
```java
818+
public String getParameterName() {
819+
ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
820+
if (discoverer != null) {
821+
String[] parameterNames = (this.method != null ?
822+
discoverer.getParameterNames(this.method) : discoverer.getParameterNames(this.constructor));
823+
if (parameterNames != null) {
824+
this.parameterName = parameterNames[this.parameterIndex];
825+
}
826+
this.parameterNameDiscoverer = null;
827+
}
828+
return this.parameterName;
829+
}
830+
```
831+
832+
显然,参数名的获取由接口ParameterNameDiscoverer完成:
833+
834+
![ParameterNameDiscoverer](images/ParameterNameDiscoverer.jpg)
835+
836+
默认采用DefaultParameterNameDiscoverer,但此类其实相当于StandardReflectionParameterNameDiscoverer和LocalVariableTableParameterNameDiscoverer的组合,且前者先于后者进行解析。
837+
838+
StandardReflectionParameterNameDiscoverer.getParameterNames:
839+
840+
```java
841+
@Override
842+
public String[] getParameterNames(Method method) {
843+
Parameter[] parameters = method.getParameters();
844+
String[] parameterNames = new String[parameters.length];
845+
for (int i = 0; i < parameters.length; i++) {
846+
Parameter param = parameters[i];
847+
if (!param.isNamePresent()) {
848+
return null;
849+
}
850+
parameterNames[i] = param.getName();
851+
}
852+
return parameterNames;
853+
}
854+
```
855+
856+
此类被注解UsesJava8标注,其原理就是利用的jdk8的-parameters编译参数,只有在加上此选项的情况下才能用反射的方法获得真实的参数名,所以一般情况下StandardReflectionParameterNameDiscoverer是无法成功获取参数名的。
857+
858+
LocalVariableTableParameterNameDiscoverer利用了ASM直接访问class文件中的本地变量表来得到变量名,下面是使用`javap -verbose`命令得到的本地变量表示例:
859+
860+
![本地变量表](images/local_variable_tables.PNG)
861+
862+
但是默认情况下javac compiler是不生成本地变量表这种调试信息的,需要加`-g`参数才可以,那为什么在我们的测试Controller中却可以获得呢,玄机就在于idea的下列设置:
863+
864+
![idea编译设置](images/idea_debug_info.PNG)
865+
866+
取消这项设置的勾选再次运行程序便出问题了:
867+
868+
![调试信息错误](images/debug_info_error.PNG)
869+
775870
#### Model
776871

777872
解析由ModelMethodProcessor完成。
@@ -803,7 +898,8 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m
803898

804899
#### 总结
805900

806-
我们可以通过实现HandlerMethodArgumentResolver接口并将其注册容器的方式实现自定义参数类型的解析。
901+
- 我们可以通过实现HandlerMethodArgumentResolver接口并将其注册容器的方式实现自定义参数类型的解析。
902+
- 为了防止出现参数名获取不到的问题,应优先使用@RequestParam注解直接声明需要的参数名称。
807903

808904
### 返回值解析
809905

0 commit comments

Comments
 (0)