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 }