HandlerAdapter在處理請求時上下文數據的傳遞工作是由ModelAndViewContainer負責的. 就是記錄HandlerMethodArgumentResolver 和 HandlerMethodReturnValueHandler 在處理handler時 使用的模型model和視...
HandlerAdapter在處理請求時上下文數據的傳遞工作是由ModelAndViewContainer負責的.
源碼註釋是這樣描述的:
Records model and view related decisions made by HandlerMethodArgumentResolvers and HandlerMethodReturnValueHandlers during the course of invocation of a controller method.
翻譯下: 記錄HandlerMethodArgumentResolver 和 HandlerMethodReturnValueHandler 在處理handler時 使用的模型model和視圖view相關信息.
ModelAndViewContainer主要職責:
1. 維護模型model,包括defaultModle和redirectModel
2. 維護視圖view
3. 維護是否redirect信息,及根據這個判斷HandlerAdapter使用的是defaultModel或redirectModel(判斷規則詳見下文)
4. 維護@SessionAttributes註解信息狀態
5. 維護handler是否處理標記
目錄:
1. ModelAndViewContainer屬性
2. 科普ModelMap繼承體系
3. ModelAndView提供的api,複雜的還是model相關的屬性設置,其他主要是簡單的getter,setter
ModelAndViewContainer屬性
先來看看ModelAndViewContainer的屬性,這樣就比較清晰:
1 package org.springframework.web.method.support; 2 public class ModelAndViewContainer { 3 // 視圖,實際使用時可能是String類型的邏輯視圖 4 private Object view; 5 // 標記handler是否已經完成請求處理 6 private boolean requestHandled = false; 7 // 預設模型,下文我們可以簡單科普下ModelMap繼承體系 8 private final ModelMap defaultModel = new BindingAwareModelMap(); 9 // redirect時使用的模型,實際使用的是RedirectAttributesModelMap 10 private ModelMap redirectModel; 11 // 標記處理器返回redirect視圖 12 private boolean redirectModelScenario = false; 13 // redirect時,是否忽略defaultModel 14 private boolean ignoreDefaultModelOnRedirect = false; 15 // @SessionAttributes註解使用狀態標記,就是是否處理完畢 16 private final SessionStatus sessionStatus = new SimpleSessionStatus(); 17 }
順便學個英語單詞,Scenario 方案
科普ModelMap繼承體系
繼續往下分析之前,我們先來簡單科普下ModelMap的繼承體系:
各個類的職責與使用場景:
ModelMap是LinkedHashMap的子類,主要是封裝attribute概念,實際處理還是委托給map
ExtendedModelMap添加鏈調用chained calls,並實現Model介面
BindingAwareModelMap,BindingResult相關屬性被設置時,自動清除BindingResult.defaultModel使用的該類.
RedirectAttributesModelMap 通過DataBinder做參數類型轉換,redirect時使用的flash attributes
各有側重地看下源碼吧
2.1 ModelMap,是繼承LinkedHashMap,添加mergeAttributes
1 package org.springframework.ui; 2 public class ModelMap extends LinkedHashMap<String, Object> { 3 public ModelMap mergeAttributes(Map<String, ?> attributes) { 4 if (attributes != null) { 5 for (String key : attributes.keySet()) { 6 if (!containsKey(key)) { 7 put(key, attributes.get(key)); 8 } 9 } 10 } 11 return this; 12 } 13 // ... 14 }
2.2 ExtendedModelMap添加鏈調用chained calls
1 package org.springframework.ui; 2 public class ExtendedModelMap extends ModelMap implements Model { 3 @Override 4 public ExtendedModelMap addAttribute(String attributeName, Object attributeValue) { 5 super.addAttribute(attributeName, attributeValue); 6 return this;// 看這裡 7 } 8 // ... 9 }
2.3 BindingAwareModelMap,BindingResult相關屬性被設置時,自動清除BindingResult.defaultModel使用的該類.
看的就是removeBindingResultIfNecessary.這邊put和putAll都會調用removeBindingResultIfNecessary
1 package org.springframework.validation.support; 2 public class BindingAwareModelMap extends ExtendedModelMap { 3 4 @Override 5 public Object put(String key, Object value) { 6 removeBindingResultIfNecessary(key, value); 7 return super.put(key, value); 8 } 9 // ... 10 private void removeBindingResultIfNecessary(Object key, Object value) { 11 if (key instanceof String) { 12 String attributeName = (String) key; 13 if (!attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) { 14 String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + attributeName; 15 BindingResult bindingResult = (BindingResult) get(bindingResultKey); 16 if (bindingResult != null && bindingResult.getTarget() != value) { 17 remove(bindingResultKey); 18 } 19 } 20 } 21 } 22 23 }
2.4 RedirectAttributesModelMap 通過DataBinder做參數類型轉換,redirect時使用的flash attributes
這樣主要就是添加了dataBinder和flashAttributes屬性相關的api
1 package org.springframework.web.servlet.mvc.support; 2 public class RedirectAttributesModelMap extends ModelMap implements RedirectAttributes { 3 4 private final DataBinder dataBinder; 5 6 private final ModelMap flashAttributes = new ModelMap(); 7 8 private String formatValue(Object value) { 9 if (value == null) { 10 return null; 11 } 12 return (dataBinder != null) ? dataBinder.convertIfNecessary(value, String.class) : value.toString(); 13 } 14 // ... 15 }
ModelAndView提供的api
主要是兩類api,view相關和model相關.
3.1 view相關其實就一個api,是否類應用(通過是否是string類型判斷):
1 package org.springframework.web.method.support; 2 public class ModelAndViewContainer { 3 // ... 4 /** 5 * Whether the view is a view reference specified via a name to be 6 * resolved by the DispatcherServlet via a ViewResolver. 7 */ 8 public boolean isViewReference() { 9 return (this.view instanceof String); 10 } 11 }
3.2 model相關的api就比較多了,主要是設置模型值.
在設置模型值的時候,這邊涉及到一個問題,就是container里有兩個model,往哪個裡設置?
container索性抽象一個getModel()的api進行判斷.
我們來說說判斷的邏輯吧:
使用defaultModel的場景:
a,不是redirect
b,redirect場景下,redirectModel為null,同時ignoreDefaultModelOnRedirect為false
剩下的就是使用redirectModel的場景了,不細說
model相關屬性操作的類都是跟addAttribute類似的邏輯,使用getModel獲取model後直接委托.所以篇幅緣故下麵的源碼以addAttribute為例,其他都只是展現api 的signature
1 package org.springframework.web.method.support; 2 public class ModelAndViewContainer { 3 // ... 4 /** 5 * Return the model to use: the "default" or the "redirect" model. 6 * <p>The default model is used if {@code "redirectModelScenario=false"} or 7 * if the redirect model is {@code null} (i.e. it wasn't declared as a 8 * method argument) and {@code ignoreDefaultModelOnRedirect=false}. 9 */ 10 public ModelMap getModel() { 11 if (useDefaultModel()) { 12 return this.defaultModel; 13 } 14 else { 15 return (this.redirectModel != null) ? this.redirectModel : new ModelMap(); 16 } 17 } 18 /** 19 * Whether to use the default model or the redirect model. 20 * 不是redirect || redirect時redirectModel為空同時不忽略defaultModel 21 */ 22 private boolean useDefaultModel() { 23 return !this.redirectModelScenario || ((this.redirectModel == null) && !this.ignoreDefaultModelOnRedirect); 24 } 25 public ModelAndViewContainer addAttribute(String name, Object value) { 26 getModel().addAttribute(name, value); 27 return this; 28 } 29 public ModelAndViewContainer addAttribute(Object value){}; 30 public ModelAndViewContainer addAllAttributes(Map<String, ?> attributes){}; 31 public ModelAndViewContainer removeAttributes(Map<String, ?> attributes){}; 32 public boolean containsAttribute(String name){}; 33 }