一、@ModelAttribute 注解
對方法標注 @ModelAttribute 注解,在調用各個目標方法前都會去調用 @ModelAttribute 標記的注解。本質上來說,允許我們在調用目標方法前操縱模型數據。
1.在 @ModelAttribute 標注的方法處向模型中存入數據
說明一下:在@ModelAttribute 標注的方法處,可以入參的類型和目標方法處允許的入參類型一致,如 @RequestParam 標注的請求參數等等。
有兩種方式:
目標方法:
@RequestMapping("/updateStudent")
public String update(Student student) {
System.out.println("student: " + student);
return "success";
}
(1)通過向入參處添加 Model 類型或 Map 類型的參數(不推薦)
@ModelAttribute
public void getStudent(@RequestParam(value = "id", required = false) String idStr, Map<String, Object> map) {
try {
Integer id = Integer.parseInt(idStr);
System.out.println("student id: " + id);
map.put("student", new Student(1, "lisi", 23));
} catch(NumberFormatException ignored) {
}
}
在調用目標方法前,"student" 會被放入到 Model 中。至於說為什麼不推薦此種用法,是因為,最終還會向 model 中添加一個 key 為 void,值為 null 的數據。如圖:

(2)通過 @ModelAttribute 注解的 value 屬性和 @ModelAttribute 標注的方法返回值(推薦)
@ModelAttribute("student")
public Student getStudent(@RequestParam(value = "id", required = false) String idStr, Map<String, Object> map) {
Student student = null;
try {
Integer id = Integer.parseInt(idStr);
System.out.println("student id: " + id);
student = new Student(1, "lisi", 23);
} catch(NumberFormatException ignored) {
}
return student;
}
在調用目標方法前,model 中的數據:
![]()
model 中只有一個鍵值對。這種寫法更加優雅。
總結:SpringMVC 在調用目標方法前,將 @ModelAttribute 注解的 value 屬性值作為 key , 返回值作為 value,存入到 model 中。
源碼分析:
org.springframework.web.bind.annotation.support.HandlerMethodInvoker#invokeHandlerMethod
1 public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
2 NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
3
4 Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
5 try {
6 boolean debug = logger.isDebugEnabled();
7 for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
8 Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName);
9 if (attrValue != null) {
10 implicitModel.addAttribute(attrName, attrValue);
11 }
12 }
13 //開始調用標注有 @ModelAttribute 注解的方法
14 for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
15 Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);
16 Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
17 if (debug) {
18 logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
19 }
20 String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value();
21 if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
22 continue;
23 }
24 ReflectionUtils.makeAccessible(attributeMethodToInvoke);
25 Object attrValue = attributeMethodToInvoke.invoke(handler, args);
26 if ("".equals(attrName)) {
27 Class<?> resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());
28 attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);
29 }
30 if (!implicitModel.containsAttribute(attrName)) {
31 implicitModel.addAttribute(attrName, attrValue);
32 }
33 }
34 Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
35 if (debug) {
36 logger.debug("Invoking request handler method: " + handlerMethodToInvoke);
37 }
38 ReflectionUtils.makeAccessible(handlerMethodToInvoke);
39 //調用目標方法
40 return handlerMethodToInvoke.invoke(handler, args);
41 }
42 catch (IllegalStateException ex) {
43 // Internal assertion failed (e.g. invalid signature):
44 // throw exception with full handler method context...
45 throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex);
46 }
47 catch (InvocationTargetException ex) {
48 // User-defined @ModelAttribute/@InitBinder/@RequestMapping method threw an exception...
49 ReflectionUtils.rethrowException(ex.getTargetException());
50 return null;
51 }
52 }
行號14 處的 for 循環就是處理 @ModleAttribute 標注的方法的,在40行處調用目標方法——在調用目標方法前調用 @ModelAttribute 標注的方法。
在 16 行處已經對請求參數做了一次解析——在@ModelAttribute 標注的方法處,可以入參的類型和目標方法處允許的入參類型一致
20行、25行、31行——第二種方式,同時也明白如果 model 中包含相同的 key 時,是不會替換的。
2.在目標方法處讀取模型中的數據
@ModelAttribute("student")
public Student getStudent() {
return new Student(1, "lisi", 23);
}
@ModelAttribute("student2")
public Student getStudent2() {
return new Student(2, "wangwu", 33);
}
(1)在目標方法入參處不使用 @ModelAttribute 注解
@RequestMapping("/updateStudent")
public String update(Student student2) {
System.out.println("student: " + student2);
return "success";
}
控制台輸出:
student: Student{id=23, studentName='lisi', age=23}
(2)在目標方法入參處使用 @ModelAttribute 注解
@RequestMapping("/updateStudent")
public String update(@ModelAttribute("student2") Student student2) {
System.out.println("student: " + student2);
return "success";
}
控制台輸出:
student: Student{id=23, studentName='wangwu', age=33}
(3)源碼分析
org.springframework.web.bind.annotation.support.HandlerMethodInvoker#resolveHandlerArguments
這個方法行數太多了,我們只看關注點:
289行:如果目標方法入參有標記 @ModelAttribute ,獲取它 的 value 屬性。
else if (ModelAttribute.class.isInstance(paramAnn)) {
ModelAttribute attr = (ModelAttribute) paramAnn;
attrName = attr.value();
annotationsFound++;
}
361行:
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) {
doBind(binder, webRequest, validate, validationHints, !assignBindingResult);
}
args[i] = binder.getTarget();
if (assignBindingResult) {
args[i + 1] = binder.getBindingResult();
i++;
}
implicitModel.putAll(binder.getBindingResult().getModel());
}
不論是對目標方法入參有沒有標注 @ModelAttribute 注解,最終都會執行到這裡。
看標紅的地方:在這裡進行解析的。
private WebDataBinder resolveModelAttribute(String attrName, MethodParameter methodParam,
ExtendedModelMap implicitModel, NativeWebRequest webRequest, Object handler) throws Exception {
// Bind request parameter onto object...
String name = attrName;
if ("".equals(name)) {
name = Conventions.getVariableNameForParameter(methodParam);
}
Class<?> paramType = methodParam.getParameterType();
Object bindObject;
if (implicitModel.containsKey(name)) {
bindObject = implicitModel.get(name);
}
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");
}
}
else {
bindObject = BeanUtils.instantiateClass(paramType);
}
WebDataBinder binder = createBinder(webRequest, bindObject, name);
initBinder(handler, name, binder, webRequest);
return binder;
}
注意:
String name = attrName;
if ("".equals(name)) {
name = Conventions.getVariableNameForParameter(methodParam);
}
如果沒有指定,則通過 Conventions.getVariableNameForParameter(methodParam) 獲取一個默認值。
if (implicitModel.containsKey(name)) {
bindObject = implicitModel.get(name);
}
從 model中獲取,最後執行綁定。
(4)總結:使用在目標方法入參處的 @ModelAttribute 只能起到一個 指定 attrName 的作用,即從 Model 獲取數據的 key。
<1>目標方法處的實體形參命名與 @ModelAttribute 方法標注的方法返回值之間沒有任何關系,只是類型有關系。
<2>在目標方法入參處不使用 @ModelAttribute 注解的情況:
不需要通過 @ModelAttribute 注解來指定需要使用哪個 @ModelAttribute 標注的方法的 value 屬性值。存在多個的話,使用默認值。
<3>在目標方法入參處需要使用 @ModelAttribute 注解的情況:
存在多個 @ModelAttribute 標注的方法,返回值為同一個類型A,且 @ModelAttribute 的 value 屬性值不同,在目標方法處,需要以 A 實體作為入參,但是需要不使用默認的 a ,而是需要使用指定
的 a2。這個時候,就需要在目標方法的入參處使用 @ModelAttribute,通過 value 屬性來指定使用哪個。
二、@SessionAttribute
1.官方說明

2.對 SessionAttribute 這裡有篇帖子總結的非常好,我這裡就不再贅述。
http://blog.sina.com.cn/s/blog_6d3c1ec601018cx1.html
3.我自己的理解:
@SessionAttribute 指的是 springmvc 的 session。向其中添加值得時候,同時會向 http session 中添加一條。在 sessionStatus.setComplete(); 的時候,會清空 sprinmvc
的 session,同時清除對應鍵的 http session 內容,但是通過,request.getSession.setAttribute() 方式添加的內容不會被清除掉。
其他情況下,springmvc session 和 http session使用情況相同。