天天看點

SpringMVC中——源碼分析 HandlerMethod的請求參數解析過程

SpringMVC中處理請求的方法叫做HandlerMethod

但是現在HandlerMethod屬于過時的方法.

private Object[] resolveHandlerArguments(Method handlerMethod, Object handler, NativeWebRequest webRequest, 
ExtendedModelMap implicitModel) throws Exception {
        Class<?>[] paramTypes = handlerMethod.getParameterTypes();
        Object[] args = new Object[paramTypes.length];

        for(int i = 0; i < args.length; ++i) {
            MethodParameter methodParam = new SynthesizingMethodParameter(handlerMethod, i);
            methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);
            GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
            String paramName = null;
            String headerName = null;
            boolean requestBodyFound = false;
            String cookieName = null;
            String pathVarName = null;
            String attrName = null;
            boolean required = false;
            String defaultValue = null;
            boolean validate = false;
            Object[] validationHints = null;
            int annotationsFound = 0;
            Annotation[] paramAnns = methodParam.getParameterAnnotations();
            Annotation[] var21 = paramAnns;
            int var22 = paramAnns.length;

            for(int var23 = 0; var23 < var22; ++var23) {
                Annotation paramAnn = var21[var23];
                if (RequestParam.class.isInstance(paramAnn)) {
                    RequestParam requestParam = (RequestParam)paramAnn;
                    paramName = requestParam.name();
                    required = requestParam.required();
                    defaultValue = this.parseDefaultValueAttribute(requestParam.defaultValue());
                    ++annotationsFound;
                } else if (RequestHeader.class.isInstance(paramAnn)) {
                    RequestHeader requestHeader = (RequestHeader)paramAnn;
                    headerName = requestHeader.name();
                    required = requestHeader.required();
                    defaultValue = this.parseDefaultValueAttribute(requestHeader.defaultValue());
                    ++annotationsFound;
                } else if (RequestBody.class.isInstance(paramAnn)) {
                    requestBodyFound = true;
                    ++annotationsFound;
                } else if (CookieValue.class.isInstance(paramAnn)) {
                    CookieValue cookieValue = (CookieValue)paramAnn;
                    cookieName = cookieValue.name();
                    required = cookieValue.required();
                    defaultValue = this.parseDefaultValueAttribute(cookieValue.defaultValue());
                    ++annotationsFound;
                } else if (PathVariable.class.isInstance(paramAnn)) {
                    PathVariable pathVar = (PathVariable)paramAnn;
                    pathVarName = pathVar.value();
                    ++annotationsFound;
                } else if (ModelAttribute.class.isInstance(paramAnn)) {
                    ModelAttribute attr = (ModelAttribute)paramAnn;
                    attrName = attr.value();
                    ++annotationsFound;
                } else if (Value.class.isInstance(paramAnn)) {
                    defaultValue = ((Value)paramAnn).value();
                } else {
                    Validated validatedAnn = (Validated)AnnotationUtils.getAnnotation(paramAnn, Validated.class);
                    if (validatedAnn != null || paramAnn.annotationType().getSimpleName().startsWith("Valid")) {
                        validate = true;
                        Object hints = validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(paramAnn);
                        validationHints = hints instanceof Object[] ? (Object[])((Object[])hints) : new Object[]{hints};
                    }
                }
            }

            if (annotationsFound > 1) {
                throw new IllegalStateException("Handler parameter annotations are exclusive choices - do not specify more than one such annotation on the same parameter: " + handlerMethod);
            }

            if (annotationsFound == 0) {
                Object argValue = this.resolveCommonArgument(methodParam, webRequest);
                if (argValue != WebArgumentResolver.UNRESOLVED) {
                    args[i] = argValue;
                } else if (defaultValue != null) {
                    args[i] = this.resolveDefaultValue(defaultValue);
                } else {
                    Class<?> paramType = methodParam.getParameterType();
                    if (!Model.class.isAssignableFrom(paramType) && !Map.class.isAssignableFrom(paramType)) {
                        if (SessionStatus.class.isAssignableFrom(paramType)) {
                            args[i] = this.sessionStatus;
                        } else if (HttpEntity.class.isAssignableFrom(paramType)) {
                            args[i] = this.resolveHttpEntityRequest(methodParam, webRequest);
                        } else {
                            if (Errors.class.isAssignableFrom(paramType)) {
                                throw new IllegalStateException("Errors/BindingResult argument declared without preceding model attribute. Check your handler method signature!");
                            }

                            if (BeanUtils.isSimpleProperty(paramType)) {
                                paramName = "";
                            } else {
                                attrName = "";
                            }
                        }
                    } else {
                        if (!paramType.isAssignableFrom(implicitModel.getClass())) {
                            throw new IllegalStateException("Argument [" + paramType.getSimpleName() + "] is of type Model or Map but is not assignable from the actual model.
 You may need to switch newer MVC infrastructure classes to use this argument.");
                        }

                        args[i] = implicitModel;
                    }
                }
            }

            if (paramName != null) {
                args[i] = this.resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);
            } else if (headerName != null) {
                args[i] = this.resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);
            } else if (requestBodyFound) {
                args[i] = this.resolveRequestBody(methodParam, webRequest, handler);
            } else if (cookieName != null) {
                args[i] = this.resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);
            } else if (pathVarName != null) {
                args[i] = this.resolvePathVariable(pathVarName, methodParam, webRequest, handler);
            } else if (attrName != null) {
                WebDataBinder binder = this.resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
                boolean assignBindingResult = args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]);
                if (binder.getTarget() != null) {
                    this.doBind(binder, webRequest, validate, validationHints, !assignBindingResult);
                }

                args[i] = binder.getTarget();
                if (assignBindingResult) {
                    args[i + 1] = binder.getBindingResult();
                    ++i;
                }

                implicitModel.putAll(binder.getBindingResult().getModel());
            }
        }

        return args;
    }
           

最終請求到了HandlerMethodInvoker的invokeHandlerMethod方法,而在調用HandlerMethod之前需要解析出方法需要的所有參數。

一,有标注的參數的解析

 Spring解析參數的第一步就是嘗試根據參數使用的标注解析出參數的值。下面分别說明每種标注的參數解析過程

1,RequestParam

   RequestParam是最常見的參數來源标注,它的意思是這個參數為浏覽器發送過來的請求參數。對于RequestParam的解析最普遍的過程就是根據參數的名字,在Request中查找相應的Parameter。它的結果有可能是一個字元串或者是字元串數組。如果沒有查到則嘗試使用預設值,同時若即沒有預設值還指定了required屬性為true則會抛出異常。有兩個特殊情況要優于從Request中直接查找,分别是Map類型的參數和MultiPart類型的Request。如果參數的類型是Map類型并且沒有指定參數的名字,則認為這個Map需要所有來自浏覽器的請求參數,它的解析過程就是将Request中的所有Parameter放到這個Map中。如果目前的請求是一個多部分請求,并且嘗試去尋找同名的檔案。

2,RequestHeader和CookieValue

  RequestHeader、CookieValue形式的參數解析與RequestParam非常類似。它們都可以指定預設值,也可以指定是否必須。預設值和是否必須使用起來也是相同的方式,沒有按header或cookieName找到對應的值,則嘗試使用預設值。最終若還是沒有解析出對應值,并且通過required指定了此參數必須則抛出異常,否則傳回null(預設情況下)。

3,PathVariable

  PathVariable辨別的參數說明這個參數來自于RequestMapping中聲明的URI中的模闆變量。RequestMapping注解在 URI 模闆變量中支援正規表達式, 文法 :{變量名:正規表達式},如@RequestMapping("index/{id:\\d+}")。

4,RequestBody

  顧名思義RequestBody注解說明參數來自于請求體中,具體的轉換過程則是在HandlerAdapter 配置的HttpMessageConverters中。一般用到這個這個注解,都需要跟用戶端協商好一組協定以約定傳輸的内容。

5,ModelAttribute

 * SpringMVC 确定目标方法 POJO 類型入參的過程

     * 1. 确定一個 key:

     * 1). 若目标方法的 POJO 類型的參數木有使用 @ModelAttribute 作為修飾, 則 key 為 POJO 類名第一個字母的小寫

     * 2). 若使用了  @ModelAttribute 來修飾, 則 key 為 @ModelAttribute 注解的 value 屬性值.

     * 2. 在 implicitModel 中查找 key 對應的對象, 若存在, 則作為入參傳入

     * 1). 若在 @ModelAttribute 标記的方法中在 Map 中儲存過, 且 key 和 1 确定的 key 一緻, 則會擷取到.

     * 3. 若 implicitModel 中不存在 key 對應的對象, 則檢查目前的 Handler 是否使用 @SessionAttributes 注解修飾,

     * 若使用了該注解, 且 @SessionAttributes 注解的 value 屬性值中包含了 key, 則會從 HttpSession 中來擷取 key 所

     * 對應的 value 值, 若存在則直接傳入到目标方法的入參中. 若不存在則将抛出異常.

     * 4. 若 Handler 沒有辨別 @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 則

     * 會通過反射來建立 POJO 類型的參數, 傳入為目标方法的參數

     * 5. SpringMVC 會把 key 和 POJO 類型的對象儲存到 implicitModel 中, 進而會儲存到 request 中.

     * 運作流程:

     * 1. 執行 @ModelAttribute 注解修飾的方法: 從資料庫中取出對象, 把對象放入到了 Map 中. 鍵為: user

     * 2. SpringMVC 從 Map 中取出 User 對象, 并把表單的請求參數賦給該 User 對象的對應屬性.

     * 3. SpringMVC 把上述對象傳入目标方法的參數.

     *

     * 注意: 在 @ModelAttribute 修飾的方法中, 放入到 Map 時的鍵需要和目标方法入參類型的第一個字母小寫的字元串一緻!

     *

     * SpringMVC 确定目标方法 POJO 類型入參的過程

     * 1. 确定一個 key:

     * 1). 若目标方法的 POJO 類型的參數木有使用 @ModelAttribute 作為修飾, 則 key 為 POJO 類名第一個字母的小寫

     * 2). 若使用了  @ModelAttribute 來修飾, 則 key 為 @ModelAttribute 注解的 value 屬性值.

     * 2. 在 implicitModel 中查找 key 對應的對象, 若存在, 則作為入參傳入

     * 1). 若在 @ModelAttribute 标記的方法中在 Map 中儲存過, 且 key 和 1 确定的 key 一緻, 則會擷取到.

     * 3. 若 implicitModel 中不存在 key 對應的對象, 則檢查目前的 Handler 是否使用 @SessionAttributes 注解修飾,

     * 若使用了該注解, 且 @SessionAttributes 注解的 value 屬性值中包含了 key, 則會從 HttpSession 中來擷取 key 所

     * 對應的 value 值, 若存在則直接傳入到目标方法的入參中. 若不存在則将抛出異常.

     * 4. 若 Handler 沒有辨別 @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 則

     * 會通過反射來建立 POJO 類型的參數, 傳入為目标方法的參數

     * 5. SpringMVC 會把 key 和 POJO 類型的對象儲存到 implicitModel 中, 進而會儲存到 request 中.

     *

     * 源代碼分析的流程

     * 1. 調用 @ModelAttribute 注解修飾的方法. 實際上把 @ModelAttribute 方法中 Map 中的資料放在了 implicitModel 中.

     * 2. 解析請求處理器的目标參數, 實際上該目标參數來自于 WebDataBinder 對象的 target 屬性

     * 1). 建立 WebDataBinder 對象:

     * ①. 确定 objectName 屬性: 若傳入的 attrName 屬性值為 "", 則 objectName 為類名第一個字母小寫.

     * *注意: attrName. 若目标方法的 POJO 屬性使用了 @ModelAttribute 來修飾, 則 attrName 值即為 @ModelAttribute

     * 的 value 屬性值

     *

     * ②. 确定 target 屬性:

     *     > 在 implicitModel 中查找 attrName 對應的屬性值. 若存在, ok

     *     > *若不存在: 則驗證目前 Handler 是否使用了 @SessionAttributes 進行修飾, 若使用了, 則嘗試從 Session 中

     * 擷取 attrName 所對應的屬性值. 若 session 中沒有對應的屬性值, 則抛出了異常.

     *     > 若 Handler 沒有使用 @SessionAttributes 進行修飾, 或 @SessionAttributes 中沒有使用 value 值指定的 key

     * 和 attrName 相比對, 則通過反射建立了 POJO 對象

     *

     * 2). SpringMVC 把表單的請求參數賦給了 WebDataBinder 的 target 對應的屬性.

     * 3). *SpringMVC 會把 WebDataBinder 的 attrName 和 target 給到 implicitModel.

     * 近而傳到 request 域對象中.

     * 4). 把 WebDataBinder 的 target 作為參數傳遞給目标方法的入參.

繼續閱讀