1. 程式人生 > 實用技巧 >[Re] SpringMVC-2

[Re] SpringMVC-2

資料輸出

Spring MVC 提供了以下幾種途徑輸出模型資料:

Map&Model

  • Spring MVC 在內部使用了一個 org.springframework.ui.Model 介面儲存模型資料。

  • Spring MVC 在呼叫方法前會建立一個隱含的模型物件(BindingAwareModelMap) 作為模型資料的儲存容器。如果方法形參為 org.springframework.ui.Modelorg.springframework.ui.ModelMapjava.util.Map 型別,Spring MVC 會將隱含模型的引用傳遞給這些形參。在方法體內,開發者可以通過這個形參物件訪問到模型中的所有資料,也可以向模型中新增新的屬性資料。

  • 底層其實都是 BindingAwareModelMap 在工作,而存在此物件中的資料最後都會被放在請求域中。

ModelAndView

控制器處理方法的返回值如果為 ModelAndView, 則其既包含檢視資訊,也包含模型資料資訊。而且資料是放在請求域中。

  • 新增模型資料
    MoelAndView addObject(String attributeName, Object attributeValue)
    ModelAndView addAllObject(Map<String, ?> modelMap)
    
  • 設定檢視
    void setView(View view)
    void setViewName(String viewName)
    

@SessionAttributes

  • 若希望在多個請求之間共用某個模型屬性資料,則可以在 控制器類(只能標記在類上) 上標註一個 @SessionAttributes,Spring MVC 將在模型中對應的屬性暫存到 HttpSession 中。
  • @SessionAttributes 除了可以通過屬性名指定需要放到會話中的屬性外,還可以通過模型屬性的物件型別指定哪些模型屬性需要放到會話中。
    @SessionAttributes(types=User.class) 會將隱含模型中所有型別為 User.class 的屬性新增到會話中
    @SessionAttributes(value={"user1", "user2"}) 會將隱含模型中屬性名為 user1 或 user2 的屬性新增到會話中
    @SessionAttributes(types={User.class, Dept.class})
    @SessionAttributes(value={"user1", "user2"}, types={Dept.class})
    
  • 可能會引發異常:如果在處理類定義處標註了 @SessionAttributes("xxx"),則嘗試從會話中獲取該屬性,並將其賦給該形參,然後再用請求訊息填充該形參物件。如果在會話中找不到對應的屬性,則丟擲 HttpSessionRequiredException 異常。所以,還是使用原生 API 來存。

測試上述功能

@SessionAttributes(value= {"msg", "attr"}, types=String.class)
@Controller
public class OutputController {

    @RequestMapping("/handle01")
    public String handle01(Map<String, Object> map) {
        // Map 型別:class org.springframework.validation.support.BindingAwareModelMap
        System.out.println("Map 型別:" + map.getClass());
        map.put("msg", "[Map] Can you hear me?");
        map.put("attr", "val1");
        return "success";
    }

    @RequestMapping("/handle02")
    public String handle02(Model model) {
        // Model 型別:class org.springframework.validation.support.BindingAwareModelMap
        System.out.println("Model 型別:" + model.getClass());
        model.addAttribute("msg", "[Model] Can you hear me?");
        model.addAttribute("attr", "val2");
        return "success";
    }

    @RequestMapping("/handle03")
    public String handle03(ModelMap modelMap) {
        // ModelMap 型別:class org.springframework.validation.support.BindingAwareModelMap
        System.out.println("ModelMap 型別:" + modelMap.getClass());
        modelMap.addAttribute("msg", "[ModelMap] Can you hear me?");
        return "success";
    }

    @RequestMapping("/handle04")
    public ModelAndView handle04() {
        ModelAndView mav = new ModelAndView();
        mav.addObject("msg", "[ModelAndView] Can you hear me?");
        mav.setViewName("success");
        return mav;
    }
}

@ModelAttribute

使用情景

  • 當修改 Book 物件時,SpringMVC 提供的要封裝請求引數的 Book 物件不應該是自己 new 出來的,而應該是從 DB 中取出來的物件。
  • 用這個準備好的物件封裝請求引數,實現對這個物件的部分屬性覆蓋。

使用註解

  • @ModelAttribute 註解可加在方法和引數上
    • 在方法定義上使用該註解:Spring MVC 在呼叫目標處理方法前,會先逐個呼叫在方法級上標註了 @ModelAttribute 的方法
    • 在方法的形參前使用該註解:可以從隱含的模型資料中獲取物件,再將請求引數繫結到物件中,再傳入形參
  • 應用於使用場景
    • 將註解加在方法上,可以在提前執行的方法中去 DB 查 Book 的資訊, 將這個 Book 資訊儲存起來(方便下一個方法還能接著使用)
    • 提前方法的形參處宣告一個 Map/Model/ModelMap,在其中存放 Book
    • 在真正的處理方法處的 Book 形參上,也加上該註解。待該方法執行時,又會自動將之前查處的 Book 資訊直接放入該形參
@RequestMapping("/updateBook")
public String updateBook(@RequestParam(value="author")String author, HttpServletRequest
        request, Map<String, Object> model, @ModelAttribute("haha")Book book){
    o2 = model;
    b2  = book;
    Object haha = model.get("haha");
    // System.out.println("傳入的Model:" + model.getClass()); // BindingAwareModelMap
    System.out.println("o1==o2?" + (o1 == o2)); // true
    System.out.println("b1==b2?" + (b1 == b2)+"-->" + (b2 == haha)); // true-->true
    System.out.println("頁面要提交過來的圖書資訊:" + book);
    return "success";
}

@ModelAttribute
public void myModelAttribute(Map<String, Object> map){
    Book book = new Book(1101, "狐狸在說什麼", "韓", 98, 10, 98.98);
    System.out.println("資料庫中查到的圖書資訊是:" + book);
    map.put("haha", book);
    b1 = book;
    o1 = map;
    System.out.println("Map的型別:" + map.getClass()); // BindingAwareModelMap
}

原始碼

doDispatcher 原始碼

  • 介面卡執行目標方法:L54 (原始碼對應 L945)
  • 請求轉發到對應檢視:L71 (原始碼對應 L959)
protected void doDispatch(HttpServletRequest request
        , HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            // 檢查是否是檔案上傳請求
            processedRequest = checkMultipart(request);
            multipartRequestParsed = processedRequest != request;
            // 根據當前的請求 URI 找到目標處理器,拿到執行鏈
            // Determine handler for the current request.
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                noHandlerFound(processedRequest, response); // 找不到:404/throw
                return;
            }

            // 拿到能執行目標處理器(類)所有方法的 [介面卡]
            // 介面卡: 反射工具, 型別為 AnnotationMethodHandlerAdapter
            // Determine handler adapter for the current request.
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request
                        , mappedHandler.getHandler());
                if (logger.isDebugEnabled()) {
                    String requestUri = urlPathHelper.getRequestUri(request);
                    logger.debug("Last-Modified value for ["
                            + requestUri + "] is: " + lastModified);
                }
                if (new ServletWebRequest(request, response)
                        .checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            // 執行所有攔截器的 preHandle()
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            try {
                // Actually invoke the handler. [介面卡] 執行目標方法,會將目標方法的返回值
                // 作為檢視名儲存到 ModelAndView 物件的 view 屬性中。注意:目標方法無論怎麼寫
                // [介面卡] 執行完成後都會將有關資訊(請求域屬性/檢視)封裝到 ModelAndView 中
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            }
            finally {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
            }

            // 如果目標方法返回 void,給其設定一個預設檢視名
            applyDefaultViewName(request, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }

        // 轉發到目標檢視 (根據 ModelAndView 封裝的 View 資訊將請求轉發
        // 到對應頁面,還可以從請求域中獲取其中封裝的 ModelMap 資料)
        processDispatchResult(processedRequest, response
                , mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Error err) {
        triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            return;
        }
        // Clean up any resources used by a multipart request.
        if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
        }
    }
}

[小結] 請求過來,DispatcherServlet 收到請求,呼叫 doDispatcher() 進行處理:

  1. getHandler() 根據當前請求地址找到能處理這個請求的類(處理器)
    根據當前請求,在 HandlerMapping 中找到這個請求的對映資訊,獲取對應的目標處理器類。
  2. getHandlerAdapter() 根據當前處理器類獲取到能執行這個處理器方法的介面卡 ha
    根據當前處理器類,找到 support 該處理器類的 HandlerAdapter(介面卡)
  3. ha.handle() 使用介面卡(AnnotationMethodHandlerAdapter) 執行目標方法,會返回一個 ModelAndView 物件
  4. processDispatchResult() 根據 ModelAndView 的資訊轉發到具體的檢視,並可以在請求域中取出對應的模型資料

getHandler 細節

返回的是目標處理器的執行鏈:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    for (HandlerMapping hm : this.handlerMappings) {
        if (logger.isTraceEnabled()) {
            logger.trace("Testing handler map [" + hm
            + "] in DispatcherServlet with name '" + getServletName() + "'");
        }
        HandlerExecutionChain handler = hm.getHandler(request);
        if (handler != null) {
            return handler;
        }
    }
    return null;
}

HandlerMapping 處理器對映;裡面儲存了每一個處理器能處理哪些請求

getHandlerAdapter 細節

如何找到目標處理器類的介面卡(要拿介面卡去執行目標方法)。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    for (HandlerAdapter ha : this.handlerAdapters) {
        if (logger.isTraceEnabled()) {
            logger.trace("Testing handler adapter [" + ha + "]");
        }
        if (ha.supports(handler)) {
            return ha;
        }
    }
    throw new ServletException("No adapter for handler [" + handler
            + "]: The DispatcherServlet configuration needs to include"
            + "a HandlerAdapter that supports this handler");
}

SpringMVC 九大元件

SpringMVC 在工作的時候,關鍵位置都是由如下屬性(元件) 來完成的,故稱為 "SpringMVC的 9 大元件" (共同點:全是介面 → 介面就是規範,提供了擴充套件性)。

/** 檔案上傳解析器 */
private MultipartResolver multipartResolver;

/** 區域資訊解析器,和國際化有關 */
private LocaleResolver localeResolver;

/** 主題解析器,支援主題效果更換 */
private ThemeResolver themeResolver;

/** Handler 對映資訊 */
private List<HandlerMapping> handlerMappings;

/** Handler 介面卡 */
private List<HandlerAdapter> handlerAdapters;

/** SpringMVC 異常解析器 */
private List<HandlerExceptionResolver> handlerExceptionResolvers;

/** 檢視名轉換,當處理器方法返回void,該解析器將檢視名設定為請求URI */
private RequestToViewNameTranslator viewNameTranslator;

/** (FlashMap + Manager) SpringMVC 中允許重定向攜帶資料(放Session域)的功能 */
private FlashMapManager flashMapManager;

/** 檢視解析器 */
private List<ViewResolver> viewResolvers;

handlerMappings 和 handlerAdapters 是什麼時候有值的?

以初始化 HandlerMappings 為例:

handle 細節

要執行的目標方法

@RequestMapping("/hello")
public String hello(@RequestParam("bookName")String bookName
        , Map<String, Object> map, HttpSession session
        , @ModelAttribute("book") Book book) {
    System.out.println("Hello~");
    return "success";
}

@ModelAttribute
public void myModelAttribute(Map<String, Object> map){
    Book book = new Book(1101, "狐狸在說什麼", "韓", 98, 10, 199.9);
    System.out.println("資料庫中查到的圖書資訊是:" + book);
    map.put("book", book);
    System.out.println("modelAttribute方法查詢圖書並儲存到Map中:" + map.getClass());
}

AnnotationMethodHandlerAdapter

@Override
public ModelAndView handle(HttpServletRequest request
        , HttpServletResponse response, Object handler) throws Exception {

    // ...

    return invokeHandlerMethod(request, response, handler);
}

protected ModelAndView invokeHandlerMethod(HttpServletRequest request
        , HttpServletResponse response, Object handler) throws Exception {
    // 拿到 [方法解析器]
    ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
    // [方法解析器] 根據當前請求解析得到當前請求的目標處理方法
    Method handlerMethod = methodResolver.resolveHandlerMethod(request);
    // 通過 [方法解析器] 來建立 [方法執行器]
    ServletHandlerMethodInvoker methodInvoker
            = new ServletHandlerMethodInvoker(methodResolver);
    // 包裝原生 request 和 response
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    // 建立本次請求的隱含模型!
    ExtendedModelMap implicitModel = new BindingAwareModelMap();

    // 真正執行目標方法:目標方法利用反射執行期間確定引數值,提前執行
    // @ModelAttribute 標註的方法等所有操作都在其中,詳見 2.5.3
    Object result = methodInvoker.invokeHandlerMethod(handlerMethod
            , handler, webRequest, implicitModel);
    ModelAndView mav = methodInvoker.getModelAndView(handlerMethod
            , handler.getClass(), result, implicitModel, webRequest);
    methodInvoker.updateModelAttributes(handler
            , (mav != null ? mav.getModel() : null), implicitModel, webRequest);
    return mav;
}


// 該方法被下面 HandlerMethodInvoker 在解析普通(沒加註解的)引數時呼叫
@Override
protected Object resolveStandardArgument(Class<?> parameterType
        , NativeWebRequest webRequest) throws Exception {
    HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
    HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);

    if (ServletRequest.class.isAssignableFrom(parameterType) ||
        MultipartRequest.class.isAssignableFrom(parameterType)) {
        Object nativeRequest = webRequest.getNativeRequest(parameterType);
        if (nativeRequest == null) {
            throw new IllegalStateException("Current request is not of type ["
                     + parameterType.getName() + "]: " + request);
        }
        return nativeRequest;
    }
    else if (ServletResponse.class.isAssignableFrom(parameterType)) {
        this.responseArgumentUsed = true;
        Object nativeResponse = webRequest.getNativeResponse(parameterType);
        if (nativeResponse == null) {
            throw new IllegalStateException("Current response is not of type ["
                     + parameterType.getName() + "]: " + response);
        }
        return nativeResponse;
    }
    else if (HttpSession.class.isAssignableFrom(parameterType)) {
        return request.getSession();
    }
    else if (Principal.class.isAssignableFrom(parameterType)) {
        return request.getUserPrincipal();
    }
    else if (Locale.class.equals(parameterType)) {
        return RequestContextUtils.getLocale(request);
    }
    else if (InputStream.class.isAssignableFrom(parameterType)) {
        return request.getInputStream();
    }
    else if (Reader.class.isAssignableFrom(parameterType)) {
        return request.getReader();
    }
    else if (OutputStream.class.isAssignableFrom(parameterType)) {
        this.responseArgumentUsed = true;
        return response.getOutputStream();
    }
    else if (Writer.class.isAssignableFrom(parameterType)) {
        this.responseArgumentUsed = true;
        return response.getWriter();
    }
    return super.resolveStandardArgument(parameterType, webRequest);
}

HandlerMethodInvoker

public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
        NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {

    Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);

    boolean debug = logger.isDebugEnabled();
    for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
        Object attrValue = this.sessionAttributeStore
                .retrieveAttribute(webRequest, attrName);
        if (attrValue != null) {
            implicitModel.addAttribute(attrName, attrValue);
        }
    }

    // 找到所有 @ModelAttribute 註解標註的方法
    for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
        Method attributeMethodToInvoke = BridgeMethodResolver
                .findBridgedMethod(attributeMethod);
        // 來解析 @ModelAttribute 方法執行所需要的每一個引數的值(該方法還傳入了隱含模型)
        Object[] args = resolveHandlerArguments(attributeMethodToInvoke
                , handler, webRequest, implicitModel);
        if (debug) {
            logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
        }

        // 將方法上標註的 @ModelAttribute 的 value值取出來賦給 attrName,沒設就給個空串""
        String attrName = AnnotationUtils.findAnnotation(
                attributeMethod, ModelAttribute.class).value();
        if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
            continue;
        }

        ReflectionUtils.makeAccessible(attributeMethodToInvoke);
        // 提前反射執行帶有 @ModelAttribute 方法,確保在目標方法執行前先執行。
        Object attrValue = attributeMethodToInvoke.invoke(handler, args);

        // 方法上標註的 @ModelAttribute 註解如果有 value 值,attrName = value的值。
        // 如果沒設定該屬性(前面給了空串),attrName 就會變為返回值型別(resolvedType)
        // 首字母小寫,比如 void、book
        if ("".equals(attrName)) {
            Class<?> resolvedType = GenericTypeResolver.resolveReturnType(
                        attributeMethodToInvoke, handler.getClass());
            attrName = Conventions.getVariableNameForReturnType(
                    attributeMethodToInvoke, resolvedType, attrValue);
        }
        // 把提前執行的 @ModelAttribute 方法的返回值也放入隱含模型中!這是該註解標在方法上的
        // 另一個作用:註解的 value 屬性值為 key,以方法執行後的返回值為 value,放入到隱含
        // 模型中,如 void=null。
        if (!implicitModel.containsAttribute(attrName)) {
            implicitModel.addAttribute(attrName, attrValue);
        }
    }

    // 來解析目標方法執行所需要的每一個引數的值(該方法還傳入了隱含模型)
    Object[] args = resolveHandlerArguments(handlerMethodToInvoke
            , handler, webRequest, implicitModel);
    if (debug) {
        logger.debug("Invoking request handler method: " + handlerMethodToInvoke);
    }
    ReflectionUtils.makeAccessible(handlerMethodToInvoke);
    // 這裡才是真正目標方法執行!
    return handlerMethodToInvoke.invoke(handler, args);
}


// 如下方法,就確定目標方法執行時使用的每一個引數的值
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 MethodParameter(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();

        // 找到目標方法的 i 位置處引數的所有註解,如果有註解就解析並儲存註解的資訊
        for (Annotation paramAnn : paramAnns) {
            if (RequestParam.class.isInstance(paramAnn)) {
                RequestParam requestParam = (RequestParam) paramAnn;
                paramName = requestParam.value();
                required = requestParam.required();
                defaultValue = parseDefaultValueAttribute(requestParam.defaultValue());
                annotationsFound++;
            }
            else if (RequestHeader.class.isInstance(paramAnn)) {
                RequestHeader requestHeader = (RequestHeader) paramAnn;
                headerName = requestHeader.value();
                required = requestHeader.required();
                defaultValue = 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.value();
                required = cookieValue.required();
                defaultValue = 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 if (paramAnn.annotationType().getSimpleName().startsWith("Valid")) {
                validate = true;
                Object value = AnnotationUtils.getValue(paramAnn);
                validationHints = (value instanceof Object[]
                         ? (Object[]) value : new Object[] {value});
            }
        }

        if (annotationsFound > 1) {
            throw new IllegalStateException(...);
        }

        // 沒找到註解的情況
        if (annotationsFound == 0) {
            // 解析普通引數,底層實際呼叫 resolveStandardArgument(paramType
            // , webRequest) 就是確定當前引數是否是 ServletAPI,詳見 #2.5.2
            Object argValue = resolveCommonArgument(methodParam, webRequest);
            // Object UNRESOLVED = new Object();
            if (argValue != WebArgumentResolver.UNRESOLVED) {
                args[i] = argValue;
            }
            else if (defaultValue != null) {
                args[i] = resolveDefaultValue(defaultValue);
            }
            else {
                Class<?> paramType = methodParam.getParameterType();
                // 判斷引數型別是否是 Model / Map 旗下的型別
                if (Model.class.isAssignableFrom(paramType)
                         || Map.class.isAssignableFrom(paramType)) {
                    if (!paramType.isAssignableFrom(implicitModel.getClass())) {
                        throw new IllegalStateException(...);
                    }
                    // 如果是,將隱含模型物件的引用賦值給該引數
                    args[i] = implicitModel;
                }
                else if (SessionStatus.class.isAssignableFrom(paramType)) {
                    args[i] = this.sessionStatus;
                }
                else if (HttpEntity.class.isAssignableFrom(paramType)) {
                    args[i] = resolveHttpEntityRequest(methodParam, webRequest);
                }
                else if (Errors.class.isAssignableFrom(paramType)) {
                    throw new IllegalStateException(...);
                }
                else if (BeanUtils.isSimpleProperty(paramType)) {
                    paramName = "";
                }
                else {
                    attrName = "";
                }
            }
        }

        // ~~~~~~~~~~~~~ 確定值的環節(有無註解都得來這) ~~~~~~~~~~~~~
        if (paramName != null) {
            args[i] = resolveRequestParam(paramName, required
                    , defaultValue, methodParam, webRequest, handler);
        }
        else if (headerName != null) {
            args[i] = resolveRequestHeader(headerName, required
                    , defaultValue, methodParam, webRequest, handler);
        }
        else if (requestBodyFound) {
            args[i] = resolveRequestBody(methodParam, webRequest, handler);
        }
        else if (cookieName != null) {
            args[i] = resolveCookieValue(cookieName, required
                    , defaultValue, methodParam, webRequest, handler);
        }
        else if (pathVarName != null) {
            args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);
        }
        else if (attrName != null) { // 確定自定義型別引數的值!
            WebDataBinder binder = resolveModelAttribute(attrName
                    , methodParam, implicitModel, webRequest, handler);
            boolean assignBindingResult = (args.length > i + 1
                    && Errors.class.isAssignableFrom(paramTypes[i + 1]));
            if (binder.getTarget() != null) {
                // 將請求引數中提交的每一個屬性和JavaBean進行繫結
                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;
}

// 確定自定義型別引數的值
private WebDataBinder resolveModelAttribute(String attrName, MethodParameter
            methodParam, ExtendedModelMap implicitModel, NativeWebRequest
            webRequest, Object handler) throws Exception {

    // Bind request parameter onto object...
    String name = attrName;
    // 如果 attrName 是空串,就將引數型別的首字母小寫作為值
    if ("".equals(name)) {
        name = Conventions.getVariableNameForParameter(methodParam);
    }

    Class<?> paramType = methodParam.getParameterType();

    // SpringMVC 確定 POJO 的 3 步
    Object bindObject;
    // 1) 如果隱含模型中有這個 key (標了 @ModelAttribute 就是註解
    // 指定的 value,沒標就是引數型別首字母小寫)
    if (implicitModel.containsKey(name)) {
        bindObject = implicitModel.get(name);
    }
    // 2) 如果是 @SessionAttributes 標註的屬性,就從 session 中拿
    else if (this.methodResolver.isSessionAttribute(name, paramType)) {
        bindObject = this.sessionAttributeStore.retrieveAttribute(webRequest, name);
        if (bindObject == null) {
            raiseSessionRequiredException("Session attribute '" + name
                     + "' required - not found in session");
        }
    }
    // 3) 如果都不是,就利用反射建立物件
    else {
        bindObject = BeanUtils.instantiateClass(paramType);
    }
    WebDataBinder binder = createBinder(webRequest, bindObject, name);
    initBinder(handler, name, binder, webRequest);
    return binder;
}

方法中每個引數的值