ModelFactory主要是兩個職責:
1. 初始化model
2. 處理器執行後將modle中相應參數設置到SessionAttributes中
我們來看看具體的處理邏輯(直接充當分析目錄):
1. 初始化model
1.1 解析類上使用的sessionAttributres,將獲取參數合並到mavContainer中
1.2 執行注解了@ModelAttribute的方法,並將結果同步到Model
參數名的生成規則:@ModelAttribute中定義的value > 方法的返回類型決定(直接往model.addAttribute的除外)
1.3 將注解@ModelAttribute方法參數(在@SessionAttributes定義范圍內)同步到model中
將方法中使用@ModelAttribute的參數跟@SessionAttribute核對,如果都定義了,需要將其參數值同步至mavContainer
2. 處理器執行後將modle中相應參數設置到SessionAttributes中
2.1 如果SessionStatus被調用了setComplete則清除sesssionAttributesHandler中緩存的數據
2.2 如果沒清除,將model中的數據同步至sessionAttributesHandler中
2.3 如果handler還沒處理完(是否需要渲染頁面),綁定BindingResult到model(如果需要的話)
上面的代碼說明在日常開發時,SessionStatus.setComplete寫在方法哪個位置都行,因為他是在方法執行後才在這邊調用,跟方法中的順序無關.
1. 初始化model
做了三個事情,詳細見源碼中的注釋吧:
1 package org.springframework.web.method.annotation; 2 public final class ModelFactory { 3 public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod) 4 throws Exception { 5 // 獲取使用@SessionAttributes注解並已經解析的參數,合並到mavContainer 6 Map<String, ?> attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request); 7 mavContainer.mergeAttributes(attributesInSession); 8 // 執行使用@ModelAttribute注解的方法,並將結果設置到mavContainer 9 invokeModelAttributeMethods(request, mavContainer); 10 // 將同時使用@ModelAttribute和@SessionAttributes注解的參數設置到mavContainer 11 for (String name : findSessionAttributeArguments(handlerMethod)) { 12 if (!mavContainer.containsAttribute(name)) { 13 Object value = this.sessionAttributesHandler.retrieveAttribute(request, name); 14 if (value == null) { 15 throw new HttpSessionRequiredException("Expected session attribute '" + name + "'"); 16 } 17 mavContainer.addAttribute(name, value); 18 } 19 } 20 } 21 // ... 22 }
1.1 解析類上使用的sessionAttributres,將獲取參數合並到mavContainer中
這部分,之前的<SpringMVC源碼解析 - HandlerAdapter - @SessionAttributes注解處理>已經講述得很細,這邊就不展開.
1.2 執行注解了@ModelAttribute的方法,並將結果同步到Model
迭代所有使用@ModelAttribute注解的方法
獲取@ModelAttribute中的value屬性值作為 model attribute,如果mavContainer中已經存在則退出
委托InvocableHandlerMethod的invokeForRequest生成屬性值.
a,獲取當前方法的調用參數
b,直接執行invoke,並返回結果
如果方法不是void的,則需要將值同步到mavContainer
a,如果方法是void,則說明用戶直接將參數通過model.addAttribute設置好值了
b,參數名的生成規則:@ModelAttribute中定義的value > 方法的返回類型決定
根據方法的返回類型決定參數名時,大致的規則如下:
String -> string(這邊就解釋我之前沒搞明白使用@ModelAttribute注解實例的最後一個情況)
List<Double> -> doubleList
c,如果mavContainer中還沒有這個參數值,則同步進去
1 package org.springframework.web.method.annotation; 2 public final class ModelFactory { 3 private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer mavContainer) 4 throws Exception { 5 // 迭代使用@ModelAttribute注解的方法 6 for (InvocableHandlerMethod attrMethod : this.attributeMethods) { 7 // 使用@ModelAttribute的value值作為 attribute name 8 String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value(); 9 if (mavContainer.containsAttribute(modelName)) { 10 continue; 11 } 12 // 委托InvocableHandlerMethod調用方法,生成值 13 Object returnValue = attrMethod.invokeForRequest(request, mavContainer); 14 // 如果方法返回值,需要將這個值同步到mavContainer中 15 if (!attrMethod.isVoid()){ 16 // 生成參數名:注解的value或者返回值類型 17 String returnValueName = getNameForReturnValue(returnValue, attrMethod.getReturnType()); 18 if (!mavContainer.containsAttribute(returnValueName)) { 19 mavContainer.addAttribute(returnValueName, returnValue); 20 } 21 } 22 } 23 } 24 // ... 25 }
看看InvocableHandlerMethod的invokeForRequest(NativeWebRequest request,ModelAndViewContainer mavContainer,Object... providedArgs)
這邊涉及到兩個封裝類:InvocableHandlerMethod和MethodParameter.
InvocableHandlerMethod封裝一個可執行的方法,在HandlerMethod基礎上添加方法參數解析的職責.
MethodParameter封裝方法定義相關的概念
具體的處理邏輯還是看代碼中的注釋吧.
1 package org.springframework.web.method.support; 2 public class InvocableHandlerMethod extends HandlerMethod { 3 public final Object invokeForRequest(NativeWebRequest request, 4 ModelAndViewContainer mavContainer, 5 Object... providedArgs) throws Exception { 6 // 生成方法調用時的參數 7 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); 8 // 霸氣的調用 9 Object returnValue = invoke(args); 10 11 return returnValue; 12 } 13 private Object[] getMethodArgumentValues( 14 NativeWebRequest request, ModelAndViewContainer mavContainer, 15 Object... providedArgs) throws Exception { 16 // 獲取參數,這邊沒有值 17 MethodParameter[] parameters = getMethodParameters(); 18 Object[] args = new Object[parameters.length]; 19 for (int i = 0; i < parameters.length; i++) { 20 MethodParameter parameter = parameters[i]; 21 // 參數名稱查找器,反射中拿不到參數名,所以使用spring的parameterNameDiscover 22 parameter.initParameterNameDiscovery(parameterNameDiscoverer); 23 // 獲取參數的目標類型,methodParam.setParameterType(result);設置.這邊具體的邏輯後面再細化 24 GenericTypeResolver.resolveParameterType(parameter, getBean().getClass()); 25 // 嘗試通過類型判斷,獲取參數的值 26 args[i] = resolveProvidedArgument(parameter, providedArgs); 27 if (args[i] != null) { 28 continue; 29 } 30 // 使用HandlerMethodArgumentResolver,判斷是否支持處理 31 if (argumentResolvers.supportsParameter(parameter)) { 32 try { 33 // 這邊直接處理,實際執行時,是通過責任鏈設計模式處理 34 args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory); 35 continue; 36 } catch (Exception ex) { 37 throw ex; 38 } 39 } 40 41 if (args[i] == null) { 42 String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); 43 throw new IllegalStateException(msg); 44 } 45 } 46 return args; 47 } 48 private Object invoke(Object... args) throws Exception { 49 // 解決權限的問題 50 ReflectionUtils.makeAccessible(this.getBridgedMethod()); 51 try { 52 return getBridgedMethod().invoke(getBean(), args); 53 } 54 catch (IllegalArgumentException | InvocationTargetExceptione) { 55 // 省略異常處理機制 56 } 57 } 58 // ... 59 }
我們再來看看參數名稱的生成規則吧:
如果@ModelAttribute中定義了value,就以value命名
如果注解中沒有定義value,則根據返回值類型定義名稱
如:String會被定義為string,List<Double>會被定義為doubleList(集合都是這樣定義的,包括array數組)
1 package org.springframework.web.method.annotation; 2 public final class ModelFactory { 3 4 public static String getNameForReturnValue(Object returnValue, MethodParameter returnType) { 5 ModelAttribute annot = returnType.getMethodAnnotation(ModelAttribute.class); 6 if (annot != null && StringUtils.hasText(annot.value())) { // 注解中定義了value 7 return annot.value(); 8 } 9 else { // 根據類型生成 10 Method method = returnType.getMethod(); 11 Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, returnType.getDeclaringClass()); 12 return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue); 13 } 14 } 15 // ... 16 }
接下來是如何根據返回值類型生成參數名稱的邏輯,挺有意思,重點展開:
這邊又根據方法的signature中定義的參數類型是否細化再衍生一個分支:
如果方法簽名中只定義Object類型,則需要根據value生成;否則根據簽名生成
1 package org.springframework.core; 2 public abstract class Conventions { 3 public static String getVariableNameForReturnType(Method method, Class resolvedType, Object value) { 4 // 如果signature定義為object,則根據value來判斷 5 if (Object.class.equals(resolvedType)) { 6 if (value == null) { 7 throw new IllegalArgumentException("Cannot generate variable name for an Object return type with null value"); 8 } 9 // 這邊的處理邏輯跟下面的很類似,不展開.差別是一個根據value,一個根據resolvedType判斷 10 return getVariableName(value); 11 } 12 13 Class valueClass; 14 // 是否是數組或集合 15 boolean pluralize = false; 16 17 if (resolvedType.isArray()) { // 數組,讀取內部元素的類型 18 valueClass = resolvedType.getComponentType(); 19 pluralize = true; 20 } 21 else if (Collection.class.isAssignableFrom(resolvedType)) { // 集合 22 // 集合內的元素類型 23 valueClass = GenericCollectionTypeResolver.getCollectionReturnType(method); 24 if (valueClass == null) { 25 if (!(value instanceof Collection)) {// 跟value再校驗一遍類型 26 throw new IllegalArgumentException( 27 "Cannot generate variable name for non-typed Collection return type and a non-Collection value"); 28 } 29 Collection collection = (Collection) value; 30 if (collection.isEmpty()) { 31 throw new IllegalArgumentException( 32 "Cannot generate variable name for non-typed Collection return type and an empty Collection value"); 33 } 34 // 獲取集合中的第一個value 35 Object valueToCheck = peekAhead(collection); 36 // 獲取value的類系 37 valueClass = getClassForValue(valueToCheck); 38 } 39 pluralize = true; 40 } 41 else { 42 valueClass = resolvedType; 43 } 44 45 String name = ClassUtils.getShortNameAsProperty(valueClass); 46 return (pluralize ? pluralize(name) : name); 47 } 48 // 獲取集合中的第一個value 49 private static Object peekAhead(Collection collection) { 50 Iterator it = collection.iterator(); 51 if (!it.hasNext()) { 52 throw new IllegalStateException( 53 "Unable to peek ahead in non-empty collection - no element found"); 54 } 55 Object value = it.next(); 56 if (value == null) { 57 throw new IllegalStateException( 58 "Unable to peek ahead in non-empty collection - only null element found"); 59 } 60 return value; 61 } 62 private static Class getClassForValue(Object value) { 63 Class valueClass = value.getClass(); 64 // 代理時根據接口獲取,遍歷時以第一個符合條件的為准 65 if (Proxy.isProxyClass(valueClass)) { 66 Class[] ifcs = valueClass.getInterfaces(); 67 for (Class ifc : ifcs) { 68 if (!ignoredInterfaces.contains(ifc)) { 69 return ifc; 70 } 71 } 72 } 73 else if (valueClass.getName().lastIndexOf('$') != -1 && valueClass.getDeclaringClass() == null) { 74 // '$' in the class name but no inner class - 75 // assuming it's a special subclass (e.g. by OpenJPA) 76 valueClass = valueClass.getSuperclass(); 77 } 78 return valueClass; 79 } 80 // 數組或結合統一添加後綴List 81 private static String pluralize(String name) { 82 //private static final String PLURAL_SUFFIX = "List"; 83 return name + PLURAL_SUFFIX; 84 } 85 86 }
1.3 將注解@ModelAttribute方法參數(在@SessionAttributes定義范圍內)同步到model中
遍歷HandlerMethod的所有參數,找出使用了@ModelAttribute注解的參數
獲取參數的名稱:注解value值 > 參數類型
核對這個參數名稱是否在@SessionAttributes注解內
如果mavContainer中還沒有該參數,繼續處理
獲取緩存在sessionAttributesHandler中的參數值
如果值為空,拋HttpSessionRequiredException
否則同步到mavContainer中
1 package org.springframework.web.method.annotation; 2 public final class ModelFactory { 3 // ... 4 public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod) 5 throws Exception { 6 // ... 7 for (String name : findSessionAttributeArguments(handlerMethod)) { 8 if (!mavContainer.containsAttribute(name)) { 9 Object value = this.sessionAttributesHandler.retrieveAttribute(request, name); 10 if (value == null) { 11 throw new HttpSessionRequiredException("Expected session attribute '" + name + "'"); 12 } 13 mavContainer.addAttribute(name, value); 14 } 15 } 16 } 17 private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) { 18 List<String> result = new ArrayList<String>(); 19 // 這邊找的是HandlerMethod的參數 20 for (MethodParameter param : handlerMethod.getMethodParameters()) { 21 if (param.hasParameterAnnotation(ModelAttribute.class)) { 22 String name = getNameForParameter(param); 23 if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, param.getParameterType())) { 24 result.add(name); 25 } 26 } 27 } 28 return result; 29 } 30 public static String getNameForParameter(MethodParameter parameter) { 31 ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class); 32 String attrName = (annot != null) ? annot.value() : null; 33 // 如果value為空,獲取參數類型解析屬性名稱 34 return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter); 35 } 36 }
2. 處理器執行後將modle中相應參數設置到SessionAttributes中
2.1 如果SessionStatus被調用了setComplete則清除sesssionAttributesHandler中緩存的數據
2.2 如果沒清除,將model中的數據同步至sessionAttributesHandler中
2.3 如果handler還沒處理完(是否需要渲染頁面),綁定BindingResult到model(如果需要的話)
還需要補充說明的是:
判斷綁定BindingResult到model時的條件(滿足任意):
a,不是其他參數綁定結果的Bindingresult
b,@SessionAttributes注解定義范圍內
c, 不是null,數組,集合,map,簡單數據類型
剩下的看代碼注釋就行了
1 package org.springframework.web.method.annotation; 2 public final class ModelFactory { 3 // ... 4 public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception { 5 if (mavContainer.getSessionStatus().isComplete()){ // 清除 6 this.sessionAttributesHandler.cleanupAttributes(request); 7 } 8 else { // 不清除,那麼就需要同步 9 this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel()); 10 } 11 12 if (!mavContainer.isRequestHandled()) { 13 updateBindingResult(request, mavContainer.getModel()); 14 } 15 } 16 17 private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception { 18 List<String> keyNames = new ArrayList<String>(model.keySet()); 19 for (String name : keyNames) { 20 Object value = model.get(name); 21 // 核對是否需要綁定BindingResult到model 22 if (isBindingCandidate(name, value)) { 23 String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name; 24 25 if (!model.containsAttribute(bindingResultKey)) { // 不是其他參數綁定的結果 26 WebDataBinder dataBinder = binderFactory.createBinder(request, value, name); 27 model.put(bindingResultKey, dataBinder.getBindingResult()); 28 } 29 } 30 } 31 } 32 33 private boolean isBindingCandidate(String attributeName, Object value) { 34 // 不是其他參數綁定的結果 35 if (attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) { 36 return false; 37 } 38 // 是否在@SessionAttributes注解定義中 39 Class<?> attrType = (value != null) ? value.getClass() : null; 40 if (this.sessionAttributesHandler.isHandlerSessionAttribute(attributeName, attrType)) { 41 return true; 42 } 43 // 不是null,數組,集合,map,簡單數據類型,則調用 44 return (value != null && !value.getClass().isArray() && !(value instanceof Collection) && 45 !(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass())); 46 } 47 }