7、SpringMVC源碼分析(2):分析HandlerAdapter.handle方法,了解handler方法的調用細節以及@ModelAttribute注解


 

  從上一篇 SpringMVC源碼分析(1) 中我們了解到在DispatcherServlet.doDispatch方法中會通過 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()) 這樣的方式來執行request的handler方法。

  先來分析一下ha.handle方法的調用過程:HandlerAdapter接口有一個抽象實現類AbstractHandlerMethodAdapter,在該抽象類中通過具體方法handle調用抽象方法handleInternal:

 1 public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {  2  3 private int order = Ordered.LOWEST_PRECEDENCE;  4  @Override  5 public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)  6 throws Exception {  7 return handleInternal(request, response, (HandlerMethod) handler);  8  }  9 //抽象方法,由具體的Adapter實現 10 protected abstract ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception; 11 12 }

   RequestMappingHandlerAdapter類繼承了抽象類AbstractHandlerMethodAdapter,實現了抽象方法 handleInternal,下面看看handleInternal方法的具體實現(需要注意,handler方法在synchronizeOnSession為true的情況下會放在同步代碼塊中進行執行):

 1 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {  2  3 //省略若干代碼...  4  5  @Override  6 protected ModelAndView handleInternal(HttpServletRequest request,  7 HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {  8  9 if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { 10 // Always prevent caching in case of session attribute management. 11 checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true); 12  } 13 else { 14 // Uses configured default cacheSeconds setting. 15 checkAndPrepare(request, response, true); 16  } 17 18 // Execute invokeHandlerMethod in synchronized block if required. 19 /* 20  * synchronizeOnSession默認為false,如果其為true,那么request對於的handler將會被放置在同步代碼塊 21  * 中進行執行。問題:什么時候???通過怎樣的方式將synchronizeOnSession設置為true??? 22 */ 23 if (this.synchronizeOnSession) { 24 HttpSession session = request.getSession(false); 25 if (session != null) { 26 Object mutex = WebUtils.getSessionMutex(session); 27 synchronized (mutex) { 28 return invokeHandleMethod(request, response, handlerMethod); 29  } 30  } 31  } 32 // 不在同步塊中執行handler方法 33 return invokeHandleMethod(request, response, handlerMethod); 34  } 35 }

   現在就分析上面代碼塊中的 invokeHandleMethod(request, response, handlerMethod) 方法的執行流程,看看在調用handler前后又完成了什么工作,同時分析出@ModelAttribute的作用。先來總體看看,然后再各個部分分別做 分析,一共分為6個步驟(step1 ~ step6):

 1 /**  2  * Invoke the @RequestMapping handler method preparing a @ModelAndView  3  * if view resolution is required.  4 */  5 private ModelAndView invokeHandleMethod(HttpServletRequest request,  6 HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {  7  8 ServletWebRequest webRequest = new ServletWebRequest(request, response);  9 10 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); 11 ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); 12 ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); 13 14 //step 1 15 //新建一個mavContainer,用於存放所有可能會用到的ModelAndView 16 ModelAndViewContainer mavContainer = new ModelAndViewContainer(); 17 18 //step 2 19 /* 20  * Attributes can be set two ways. The servlet container may set attributes 21  * to make available custom information about a request. For example, for 22  * requests made using HTTPS, the attribute 23  * <code>javax.servlet.request.X509Certificate</code> can be used to 24  * retrieve information on the certificate of the client. Attributes can 25  * also be set programatically using {@link ServletRequest#setAttribute}. 26  * This allows information to be embedded into a request before a 27  * {@link RequestDispatcher} call. 28  * 29  * RequestContextUtils.getInputFlashMap(request)可以獲取到request中的attribute, 30  * 並且將所有的request中的attribute放置在mavContainer中 31 */ 32  mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); 33 34 //step 3 35 /* 36  * 會在這個方法里將所有標注了@ModelAttribute的方法調用一遍,並且將該方法相關的ModelAndView放入到mavContainer中: 37  * 1、最常見的就是@ModelAttribute標注的方法入參中有Map,Model 38  * 2、如果方法有返回值,那么也會結果處理后放入到mavContainer中(是否只有ModelAndView類型的返回值才能放,有待考察??) 39 */ 40  modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); 41 mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); 42 43 //step 4 44 //許多和 asyncManager 相關的東西,這個貌似和攔截器有關。 45 AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); 46 asyncWebRequest.setTimeout(this.asyncRequestTimeout); 47 48 final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 49 asyncManager.setTaskExecutor(this.taskExecutor); 50  asyncManager.setAsyncWebRequest(asyncWebRequest); 51 asyncManager.registerCallableInterceptors(this.callableInterceptors); 52 asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); 53 54 if (asyncManager.hasConcurrentResult()) { 55 Object result = asyncManager.getConcurrentResult(); 56 mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; 57  asyncManager.clearConcurrentResult(); 58 59 if (logger.isDebugEnabled()) { 60 logger.debug("Found concurrent result value [" + result + "]"); 61  } 62 requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result); 63  } 64 65 //step 5 66 /* 67  * Invokes the method and handles the return value through one of the configured {@link HandlerMethodReturnValueHandler}s. 68  * 在這里調用handler方法 69 */ 70  requestMappingMethod.invokeAndHandle(webRequest, mavContainer); 71 72 //step 6 73 //要么返回ModelAndView,要么返回null 74 if (asyncManager.isConcurrentHandlingStarted()) { 75 return null; 76  } 77 return getModelAndView(mavContainer, modelFactory, webRequest); 78 }

   

  下面從step1~step6逐一的進行分析。

 

  step1:ModelAndViewContainer mavContainer = new ModelAndViewContainer();

  首先看一下ModelMap是如何定義的,這對理解ModelAndViewContainer以及后面的代碼有幫助,主要是理解:ModelMap實際上就是一個LinkedHashMap,而且這個Map的”值“是Object類型,能夠放置所有類型的Java對象

/** * 可以看出ModelMap實際上就是一個LinkedHashMap,且“值”為超類Object類型 * 能夠放置所有的Java對象 */ public class ModelMap extends LinkedHashMap<String, Object> { public ModelMap() { } // 調用這個構造函數之前會先調用其父類構造函數,得到一個Map對象 public ModelMap(String attributeName, Object attributeValue) { addAttribute(attributeName, attributeValue); } // 就是將attributeValue對象放置到Map末尾,同時指定鍵值為attributeName public ModelMap addAttribute(String attributeName, Object attributeValue) { put(attributeName, attributeValue); return this; } // attributes是一個Map集合;所謂merge無非是將attributes這個集合放置到現有集合的末尾 public ModelMap mergeAttributes(Map<String, ?> attributes) { if (attributes != null) { for (Map.Entry<String, ?> entry : attributes.entrySet()) { String key = entry.getKey(); if (!containsKey(key)) { put(key, entry.getValue()); } } } return this; } //省略一些方法的定義... }

  再來看ModelAndViewContainer到底是個什么東西,從下面的ModelAndViewContainer定義中不難理解其含有兩個ModelMap對象defaultModel和redirectModel默認情況下使用defaultModel,也可以通過其方法設置使用redirectModel。

 1 /**  2  * 定義了兩個ModelMap對象:defaultModel和redirectModel,實際上也就是兩個Map<String, Object>  3  * 其中defaultModel已經完成了初始化。默認使用defaultModel。  4  * 也可以通過其中的方法來設置使用redirectModel,這個是方便移植和使用其它框架而設定的。  5 */  6 public class ModelAndViewContainer {  7  8 private boolean ignoreDefaultModelOnRedirect = false;  9   // view的用法值得去探究 10 private Object view; 11 12 /* BindingAwareModelMap實際上也就是一個Map, 13  * 看看定義 public class BindingAwareModelMap extends ModelMap implements Model 14  * 15  * 從這里可以看出,ModelAndViewContainer對象都有一個默認的ModelMap 16 */ 17 private final ModelMap defaultModel = new BindingAwareModelMap(); 18 19 // 這個為方便移植其它框架的Model而設置的,SpringMVC本身使用的是defaultModel 20 private ModelMap redirectModel; 21 22 private boolean redirectModelScenario = false; 23 24 private final SessionStatus sessionStatus = new SimpleSessionStatus(); 25 26 private boolean requestHandled = false; 27 28 /** 29  * Set a view name to be resolved by the DispatcherServlet via a ViewResolver. 30  * Will override any pre-existing view name or View. 31 */ 32 public void setViewName(String viewName) { 33 this.view = viewName; 34  } 35 36 /** 37  * 返回"default" 或者是 "redirect" 模型,具體根據redirectModelScenario等 38  * 屬性的值來確定(具體用法參看javadoc) 39 */ 40 public ModelMap getModel() { 41 if (useDefaultModel()) { 42 return this.defaultModel; 43  } 44 else { 45 return (this.redirectModel != null) ? this.redirectModel : new ModelMap(); 46  } 47  } 48 49 // 返回默認的Model,而不考慮其它的屬性值如何 50 public ModelMap getDefaultModel() { 51 return this.defaultModel; 52  } 53 54 }

  到現在為止,step1完成的工作已經分析完全。

 

  step2:  mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request))

/* * 
* 檢索request域中的attribute,並將其放置在mavContainer末尾 * * RequestContextUtils.getInputFlashMap(request)會調用HttpServletRequest.getAttribute方法。 * 可以獲取到request中的attribute屬性。這個attribute就是我們屬性的attribute,它可以通過兩 * 種方式來設置:①servlet容器為request設置的;②通過ServletRequest.setAttribute方法來設置。 * */ mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));

 

 

  step3modelFactory.initModel(webRequest, mavContainer, requestMappingMethod)

  這個方法的作用在javadoc中描述得很清楚:

  Populate the model in the following order:

 

  1. Retrieve "known" session attributes listed as @SessionAttributes.
  2. Invoke @ModelAttribute methods
  3. Find @ModelAttribute method arguments also listed as @SessionAttributes and ensure they're present in the model raising an exception if necessary.

 

  也就是說初始化模型的時候會按順序完成三件事情

  ①、檢索現有的session域中的attributes,並將其放置於mavContainer末尾;

  ②、調用所有@ModelAttribute注解標注的方法;

  ③、找出handler方法中使用@ModelAttribute注解修飾的入參(主要是@ModelAttribute指定的value屬性值,如果沒有指定則是類名第一個字母小寫得到,我們假定它為V),如果V同時被@SessionAttributes的value屬性值指定,那么就必須保證在此時的mavContainer中必須含有”key“為V的對象。如果沒有,則會拋出一個異常

  

  帶着這個印象我們分析代碼就會容易很多:

 1 /*  2  * 會在這個方法里將所有標注了@ModelAttribute的方法調用一遍,並且將該方法相關的ModelAndView放入到mavContainer中:  3  * 1、最常見的就是@ModelAttribute標注的方法入參中有Map,Model  4  * 2、如果方法有返回值,那么也會結果處理后放入到mavContainer中(是否只有ModelAndView類型的返回值才能放,有待考察??)  5 */  6 modelFactory.initModel(webRequest, mavContainer, requestMappingMethod){  7 //完成①:檢索現有的session域中的attributes,並將其放置於mavContainer末尾  8 Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);  9  mavContainer.mergeAttributes(sessionAttributes); 10 11 //完成②:將所有的@ModelAttribute標注的方法都調用一遍。調用完了以后,將調用方法的結果放置到mavContainer中 12  invokeModelAttributeMethods(request, mavContainer){ 13 //modelMethods中包含了所有@ModelAttribute標注的方法,在這個while循環中將會把所有@ModelAttribute標注 14 //的方法都調用一遍 15 while (!this.modelMethods.isEmpty()) { 16 InvocableHandlerMethod attrMethod = getNextModelMethod(mavContainer).getHandlerMethod(); 17 String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value(); 18 //如果,mavContainer中已經包含有modelName名的attribute,那么,將不會調用@ModelAttribute標注的方法 19 if (mavContainer.containsAttribute(modelName)) { 20 continue; 21  } 22 23 //真正的調用@ModelAttribute標注的方法 24 Object returnValue = attrMethod.invokeForRequest(request, mavContainer); 25 26 //如果@ModelAttribute標注的方法不是void類型,則將其返回結果轉換成returnValueName;如果mavContainer中 27 //沒有包含returnValueName,則將方法返回的結果放置到mavContainer中。 28 if (!attrMethod.isVoid()){ 29 String returnValueName = getNameForReturnValue(returnValue, attrMethod.getReturnType()); 30 if (!mavContainer.containsAttribute(returnValueName)) { 31  mavContainer.addAttribute(returnValueName, returnValue); 32  } 33  } 34  } 35  }; 36 37 38 // 完成③: 39 40 /* 41  * 找出handler方法中使用@ModelAttribute注解修飾的入參(主要是@ModelAttribute指定的value屬性值, 42  * 如果沒有指定則是類名第一個字母小寫得到,我們假定它為V),如果V同時被@SessionAttributes的value 43  * 屬性值指定,則將這樣的V放入到nameList中。 44  * 45 */ 46 List<String> nameList = findSessionAttributeArguments(handlerMethod){ 47 List<String> result = new ArrayList<String>(); 48 //遍歷處理方法的所有參數 49 for (MethodParameter parameter : handlerMethod.getMethodParameters()) { 50 //如果處理方法有@ModelAttribute標注 51 if (parameter.hasParameterAnnotation(ModelAttribute.class)) { 52 //得到參數的String型的名字 53 String name = getNameForParameter(parameter); 54 //如果在處理方法所在的類定義處使用了@SessionAttributes注解,那么就將該參數放入到List中 55 if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, parameter.getParameterType())) { 56  result.add(name); 57  } 58  } 59  } 60 return result; 61  }; 62 63 // 保證nameList中記錄的V必須在mavContainer中存在,如果不存在則會拋出異常 64 for (String name : nameList) { 65 //如果mavContainer中沒有包含有name名字的attribute,那么再一次檢查request中是否包含了name名字的attribute 66 if (!mavContainer.containsAttribute(name)) { 67 //再一次檢查request中是否包含了name名字的attribute 68 Object value = this.sessionAttributesHandler.retrieveAttribute(request, name); 69 //如果沒有檢測到,則會拋出一個異常 70 if (value == null) { 71 throw new HttpSessionRequiredException("Expected session attribute '" + name + "'"); 72  } 73 //如果檢測到了,那么會將這個attribute放入到mavContainer中 74  mavContainer.addAttribute(name, value); 75  } 76  } 77 };

 

  step4: 暫時不做分析...

 

  step5: 調用handler方法同時處理返回結果

  1 requestMappingMethod.invokeAndHandle(webRequest, mavContainer){  2 //一、 調用處理方法,並的到返回結果  3 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs){  4 //為調用處理方法准備參數  5 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs){  6  7 //...  8  9 //從這里可以看出@ModelAttribute能夠修飾handler的入參  10 String name = ModelFactory.getNameForParameter(parameter){  11 ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class);  12 //如果當前參數使用了@ModelAttribute標注,則獲取到該標簽的value屬性值  13 String attrName = (annot != null) ? annot.value() : null;  14 //如果attrName有text,則返回該attrName值,也就是@ModelAttribute的value屬性值。  15 //反之,則返回參數類型第一個字母小寫后得到的字符串  16 return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter);  17  };  18  19 //檢測mavContainer中是否包含有name關鍵字的ModelAndView,如果包含有,則獲取它並返回。如果沒有,則創建一個attribute  20 Object attribute = (mavContainer.containsAttribute(name) ?  21  mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest));  22  23 //對於WebDataBinder而言,最重要的參數就是name和attribute  24 //將請求域中表單數據綁定到上面的到的attribute中:request域中有的就重新賦值,沒有的就保持原有的屬性值不變。  25 /*  26  * 結合前面name和attribute的獲取過程可以分析出request表單數據綁定的過程:  27  * ①、如果handler的入參使用了@ModelAttribute,同時還指定了其value屬性值,那么attrName就是其value屬性值;  28  * ②、如果handler的入參處沒有指定@ModelAttribute的value屬性值,或者是根本就沒有使用該注解,那么其  29  * attrName就是參數類名第一個字母小寫的到;  30  * ③、搜索mavContainer中是否有鍵值為attrName的attribute對象,如果有,則將這個對象作為表單數據綁定的對象;  31  * 如果沒有,則新創建一個作為數據綁定的對象;  32 */  33 WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);  34 if (binder.getTarget() != null) {  35 //綁定參數  36  bindRequestParameters(binder, webRequest);  37  validateIfApplicable(binder, parameter);  38 if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {  39 throw new BindException(binder.getBindingResult());  40  }  41  }  42  43 // Add resolved attribute and BindingResult at the end of the model  44 Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();  45 //刪除原有的attribute  46  mavContainer.removeAttributes(bindingResultModel);  47 //將處理過的attribute添加到末尾,這樣mavContainer中相對應的attribute就是更新以后的attribute  48  mavContainer.addAllAttributes(bindingResultModel);  49  50  51 //返回需要格式的args  52 return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);  53  };  54  55 //**真正的調用處理方法,此時的args是依據request表單數據更新過的args  56 Object returnValue = doInvoke(args);  57  58 //返回處理方法返回的結果,這個結果將會進一步處理  59 return returnValue;  60  };  61  62 //二、處理目標方法的返回結果  63 //設置應答狀態  64  setResponseStatus(webRequest);  65  66 if (returnValue == null) {  67 if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {  68 mavContainer.setRequestHandled(true);  69 return;  70  }  71  }  72 else if (StringUtils.hasText(this.responseReason)) {  73 mavContainer.setRequestHandled(true);  74 return;  75  }  76  77 mavContainer.setRequestHandled(false);  78 try {  79 //處理返回結果  80 this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest){  81 //獲取返回結果對應的處理方法  82 HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType);  83 Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]"); 84 //利用獲取到的結果處理方法來處理結果 85 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest){ 86 if (returnValue == null) { 87 return; 88 } 89 else if (returnValue instanceof String) { 90 String viewName = (String) returnValue; 91 mavContainer.setViewName(viewName); 92 if (isRedirectViewName(viewName)) { 93 mavContainer.setRedirectModelScenario(true); 94 } 95 } 96 else { 97 // should not happen 98 throw new UnsupportedOperationException("Unexpected return type: " + 99 returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); 100 } 101 }; 102 }; 103 } 104 105 };

  從上面的代碼分析可以得出表單數據綁定的流程:

  1、request的表單數據綁定首先需要創建一個WebDataBinder對象: binder = binderFactory.createBinder(webRequest, attribute, name)。表單數據在webRequest中,還要確定兩個關鍵的參數:attribute【Object類型】, name【String類型】。

  2、確定name(也就是attrName):

    ①、如果handler的入參處使用了@ModelAttribute注解,同時該注解還制定了value屬性值,那么name就是value的屬性值;

    ②、如果handler的入參數使用了@ModelAttribute注解,但是沒有指定value屬性值;或者是,入參處根本就沒有使用@ModelAttribute注解;那么這2種情況下其name值就是handler入參類名第一個字母小寫得到的String;

  3、確定attribute:

    ①、查看mavContainer中是否包含有key=name的attribute對象(mavContainer.getModel()實際上得到的是一個Map<String, Object>)。如果有,則attribute就是該對象;如果沒有,則新創建一個對象賦給attribute。其代碼如下:

    Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest));

  4、通過已經確定好的attribute和name就能夠完成數據的綁定了。

 

  step6: 返回ModelAndView

1 if (asyncManager.isConcurrentHandlingStarted()) {
2     return null;
3 }
4 
5 return getModelAndView(mavContainer, modelFactory, webRequest);

 

 

  返回有兩種情況,第一個貌似和同步機制有關,asyncManager的工作機制后續繼續分析。這里主要是分析getModelAndView(mavContainer, modelFactory, webRequest)方法的源碼。

 1 private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
 2         ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
 3 
 4     modelFactory.updateModel(webRequest, mavContainer);
 5     if (mavContainer.isRequestHandled()) {
 6         return null;
 7     }
 8     ModelMap model = mavContainer.getModel();
 9     ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
10     if (!mavContainer.isViewReference()) {
11         mav.setView((View) mavContainer.getView());
12     }
13     if (model instanceof RedirectAttributes) {
14         Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
15         HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
16         RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
17     }
18     return mav;
19 }

 

  視圖模型是一個大的主題,后面再仔細分析。

 

 

 

 

 

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM