Controller 方法参数绑定

下面是分析过程,最后有结果。

环境:
JDK_1.8
spring 4.3.16.RELEASE

目录结构:


Controller 方法参数绑定_第1张图片
项目目录

Controller 方法参数绑定_第2张图片
Controller

分析过程

在Controller中设置断点,启动项目后,发送GET请求http://localhost:8080/test/demo/user?name=goaler

观察方法调用栈,可以看到请求处理的过程


Controller 方法参数绑定_第3张图片
方法调用栈

通过查看每个方法的内容,发现getU方法的实际调用处在ServletInvocableHandlerMethod(InvocableHandlerMethod).doInvoke(Object...) line: 205
方法内容:

     
    protected Object doInvoke(Object... args) throws Exception {
        ReflectionUtils.makeAccessible(getBridgedMethod());
        try {
            //调用方法,此处args已有值
            return getBridgedMethod().invoke(getBean(), args);
        }
        catch (IllegalArgumentException ex) {
            //省略。。。
        }
        catch (InvocationTargetException ex) {
            //省略。。。
        }
    }

调用方法处,args参数已经有值,往上一个方法寻找
ServletInvocableHandlerMethod(InvocableHandlerMethod).invokeForRequest(NativeWebRequest, ModelAndViewContainer, Object...) line: 133

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        //此处获取方法参数值
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
                    "' with arguments " + Arrays.toString(args));
        }
        Object returnValue = doInvoke(args);
        if (logger.isTraceEnabled()) {
            logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
                    "] returned [" + returnValue + "]");
        }
        return returnValue;
    }

进入getMethodArgumentValues(request, mavContainer, providedArgs);这个方法是获取方法参数的

Controller 方法参数绑定_第4张图片
image.png

在149行设置断点重新调试,然后单行执行。执行到160行时args中已有值。
进入158行的resolveArgument方法
Controller 方法参数绑定_第5张图片
image.png

再121行设置断点单步调试,进入
Controller 方法参数绑定_第6张图片
resolveArgument

然后单行执行,发现返回值arg在103行被赋值。
回退,单行执行到103行,单步执行进入resolveName方法
Controller 方法参数绑定_第7张图片
resolveName

同理,返回值arg在177行被赋值,而paramValues是通过request.getParameterValues(name)直接取值的,那么这个 方法参数名是如何获得的呢
下面寻找这个name的来源
后退到resolveArgument(此处往上数第二个截图),从97,103行看出name值来源于namedValueInfo.name
那么进入getNamedValueInfo方法查看namedValueInfo的来源

private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
        //此处从缓存获取,因为是第一次执行所以获取不到
        NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
        if (namedValueInfo == null) {
            //根据参数前的注解@RequestParam获取参数名称,本demo没有注解所以创建了一个名称为""的namedValueInfo
            namedValueInfo = createNamedValueInfo(parameter);
            //通过其他方式获取参数名称
            namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
            this.namedValueInfoCache.put(parameter, namedValueInfo);
        }
        return namedValueInfo;
    }

进入updateNameValueInfo方法

/**
 * Create a new NamedValueInfo based on the given NamedValueInfo with sanitized values.
 */
private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {   
        String name = info.name;
        //如果没有获得参数名称
        if (info.name.isEmpty()) {
            //参数名称在这里取得
            name = parameter.getParameterName();
            if (name == null) {
                throw new IllegalArgumentException(
                        "Name for argument type [" + parameter.getNestedParameterType().getName() +
                        "] not available, and parameter name information not found in class file either.");
            }
        }
        String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
        return new NamedValueInfo(name, info.required, defaultValue);
    }

进入parameter.getParameterName();

/**
     * Return the name of the method/constructor parameter.
     * @return the parameter name (may be {@code null} if no
     * parameter name metadata is contained in the class file or no
     * {@link #initParameterNameDiscovery ParameterNameDiscoverer}
     * has been set to begin with)
     */
    public String getParameterName() {
        ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
        if (discoverer != null) {
            //此处this.method 不为null
            String[] parameterNames = (this.method != null ?
                    discoverer.getParameterNames(this.method) : discoverer.getParameterNames(this.constructor));
            if (parameterNames != null) {
                this.parameterName = parameterNames[this.parameterIndex];
            }
            this.parameterNameDiscoverer = null;
        }
        return this.parameterName;
    }

进入discoverer.getParameterNames(this.method)

public String[] getParameterNames(Method method) {
        for (ParameterNameDiscoverer pnd : this.parameterNameDiscoverers) {
            String[] result = pnd.getParameterNames(method);
            if (result != null) {
                return result;
            }
        }
        return null;
    }

此处Discoverers中有两个discoverer
org.springframework.core.StandardReflectionParameterNameDiscoverer,
jdk8可以通过反射获取参数名称,但是需要使用-parameters参数开启这个功能
org.springframework.core.LocalVariableTableParameterNameDiscoverer
解析字节码文件获取参数名称
这里是通过解析字节码文件获取参数名称
进入LocalVariableTableParameterNameDiscoverer的getParameterNames方法

public String[] getParameterNames(Method method) {
        Method originalMethod = BridgeMethodResolver.findBridgedMethod(method);
        Class declaringClass = originalMethod.getDeclaringClass();
        //缓存获取
        Map map = this.parameterNamesCache.get(declaringClass);
        if (map == null) {
            map = inspectClass(declaringClass);
            this.parameterNamesCache.put(declaringClass, map);
        }
        if (map != NO_DEBUG_INFO_MAP) {
            return map.get(originalMethod);
        }
        return null;
    }

inspectClass方法就是从字节码中读取方法的参数
该方法读取declaringClass类所有方法的参数名,并返回map。map的key是方法方法,value是参数名构成的string数组。
获取到方法参数名后依次返回,最终通过获取到的参数名称,从request中获取参数的值,然后invoke(obj,args)调用。

执行过程:

获取方法参数值

/**
     * Get the method argument values for the current request.
     */
    private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        //这里的参数还没有拿到参数名称
        MethodParameter[] parameters = getMethodParameters();
        //存储方法参数值
        Object[] args = new Object[parameters.length];
        //循环获取参数值
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = resolveProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            if (this.argumentResolvers.supportsParameter(parameter)) {
                try {
                    //获取参数值
                    args[i] = this.argumentResolvers.resolveArgument(
                            parameter, mavContainer, request, this.dataBinderFactory);
                    continue;
                }
                catch (Exception ex) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
                    }
                    throw ex;
                }
            }
            if (args[i] == null) {
                throw new IllegalStateException("Could not resolve method parameter at index " +
                        parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() +
                        ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
            }
        }
        return args;
    }

getNamedValueInfo(parameter);
获取方法参数名称

resolveName(resolvedName.toString(), nestedParameter, webRequest);
根据方法参数名称获取参数值

@Override
    public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //获取方法参数名称
        NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
        MethodParameter nestedParameter = parameter.nestedIfOptional();

        Object resolvedName = resolveStringValue(namedValueInfo.name);
        if (resolvedName == null) {
            throw new IllegalArgumentException(
                    "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
        }
        //根据方法参数名称从request中获取参数值
        Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
        if (arg == null) {
            if (namedValueInfo.defaultValue != null) {
                arg = resolveStringValue(namedValueInfo.defaultValue);
            }
            else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }
            arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        }
        else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = resolveStringValue(namedValueInfo.defaultValue);
        }

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
            try {
                arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
            }
            catch (ConversionNotSupportedException ex) {
                throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
                        namedValueInfo.name, parameter, ex.getCause());
            }
            catch (TypeMismatchException ex) {
                throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
                        namedValueInfo.name, parameter, ex.getCause());

            }
        }

        handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

        return arg;
    }

获取方法参数名称过程

private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
        NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
        if (namedValueInfo == null) {
            //根据注解获取参数名称
            namedValueInfo = createNamedValueInfo(parameter);
            //再次尝试获取参数名称
            namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
            this.namedValueInfoCache.put(parameter, namedValueInfo);
        }
        return namedValueInfo;
    }
private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
        String name = info.name;
        if (info.name.isEmpty()) {
            //获取参数名称
            name = parameter.getParameterName();
            if (name == null) {
                throw new IllegalArgumentException(
                        "Name for argument type [" + parameter.getNestedParameterType().getName() +
                        "] not available, and parameter name information not found in class file either.");
            }
        }
        String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
        return new NamedValueInfo(name, info.required, defaultValue);
    }
public String getParameterName() {
        ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
        if (discoverer != null) {
            String[] parameterNames = (this.method != null ?
                    discoverer.getParameterNames(this.method) : discoverer.getParameterNames(this.constructor));
            if (parameterNames != null) {
                this.parameterName = parameterNames[this.parameterIndex];
            }
            this.parameterNameDiscoverer = null;
        }
        return this.parameterName;
    }
public String[] getParameterNames(Method method) {
        Method originalMethod = BridgeMethodResolver.findBridgedMethod(method);
        Class declaringClass = originalMethod.getDeclaringClass();
        Map map = this.parameterNamesCache.get(declaringClass);
        if (map == null) {
              //从字节码文件中获取参数名称
            map = inspectClass(declaringClass);
            this.parameterNamesCache.put(declaringClass, map);
        }
        if (map != NO_DEBUG_INFO_MAP) {
            return map.get(originalMethod);
        }
        return null;
    }

总结

spring mvc先获取参数注解中的参数名称,如果没有获取到在通过反射或解析字节码文件获取参数名称,
获取到参数名称后,根据参数名称从request中获取参数的值,然后调用方法的invoke(obj,args)执行方法

你可能感兴趣的:(Controller 方法参数绑定)