@@ -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
773801name就是方法的参数名,可以看出,参数解析** 就是根据参数名去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