对方法标注 @ModelAttribute 注解,在调用各个目标方法前都会去调用 @ModelAttribute 标记的注解。本质上来讲,容许咱们在调用目标方法前操纵模型数据。html
1.在 @ModelAttribute 标注的方法处向模型中存入数据java
说明一下:在@ModelAttribute 标注的方法处,能够入参的类型和目标方法处容许的入参类型一致,如 @RequestParam 标注的请求参数等等。web
有两种方式:spring
目标方法:session
@RequestMapping("/updateStudent") public String update(Student student) { System.out.println("student: " + student); return "success"; }
@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 的数据。如图:mvc
@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 中的数据:app
model 中只有一个键值对。这种写法更加优雅。源码分析
总结:SpringMVC 在调用目标方法前,将 @ModelAttribute 注解的 value 属性值做为 key , 返回值做为 value,存入到 model 中。ui
源码分析:this
org.springframework.web.bind.annotation.support.HandlerMethodInvoker#invokeHandlerMethod
public final Object invokeHandlerMethod(Method handlerMethod, Object handler, NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception { Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod); try { boolean debug = logger.isDebugEnabled(); for (String attrName : this.methodResolver.getActualSessionAttributeNames()) { Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName); if (attrValue != null) { implicitModel.addAttribute(attrName, attrValue); } } //开始调用标注有 @ModelAttribute 注解的方法 for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) { Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod); Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel); if (debug) { logger.debug("Invoking model attribute method: " + attributeMethodToInvoke); } String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value(); if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) { continue; } ReflectionUtils.makeAccessible(attributeMethodToInvoke); Object attrValue = attributeMethodToInvoke.invoke(handler, args); if ("".equals(attrName)) { Class<?> resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass()); attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue); } if (!implicitModel.containsAttribute(attrName)) { implicitModel.addAttribute(attrName, attrValue); } } Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel); if (debug) { logger.debug("Invoking request handler method: " + handlerMethodToInvoke); } ReflectionUtils.makeAccessible(handlerMethodToInvoke); //调用目标方法 return handlerMethodToInvoke.invoke(handler, args); } catch (IllegalStateException ex) { // Internal assertion failed (e.g. invalid signature): // throw exception with full handler method context... throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex); } catch (InvocationTargetException ex) { // User-defined @ModelAttribute/@InitBinder/@RequestMapping method threw an exception... ReflectionUtils.rethrowException(ex.getTargetException()); return null; } }
行号14 处的 for 循环就是处理 @ModleAttribute 标注的方法的,在40行处调用目标方法——在调用目标方法前调用 @ModelAttribute 标注的方法。
在 16 行处已经对请求参数作了一次解析——在@ModelAttribute 标注的方法处,能够入参的类型和目标方法处容许的入参类型一致
20行、25行、31行——第二种方式,同时也明白若是 model 中包含相同的 key 时,是不会替换的。
@ModelAttribute("student") public Student getStudent() { return new Student(1, "lisi", 23); } @ModelAttribute("student2") public Student getStudent2() { return new Student(2, "wangwu", 33); }
@RequestMapping("/updateStudent") public String update(Student student2) { System.out.println("student: " + student2); return "success"; }
控制台输出:
student: Student{id=23, studentName='lisi', age=23}
@RequestMapping("/updateStudent") public String update(@ModelAttribute("student2") Student student2) { System.out.println("student: " + student2); return "success"; }
控制台输出:
student: Student{id=23, studentName='wangwu', age=33}
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中获取,最后执行绑定。
<1>目标方法处的实体形参命名与 @ModelAttribute 方法标注的方法返回值之间没有任何关系,只是类型有关系。
<2>在目标方法入参处不使用 @ModelAttribute 注解的状况:
不须要经过 @ModelAttribute 注解来指定须要使用哪一个 @ModelAttribute 标注的方法的 value 属性值。存在多个的话,使用默认值。
<3>在目标方法入参处须要使用 @ModelAttribute 注解的状况:
存在多个 @ModelAttribute 标注的方法,返回值为同一个类型A,且 @ModelAttribute 的 value 属性值不一样,在目标方法处,须要以 A 实体做为入参,可是须要不使用默认的 a ,而是须要使用指定
的 a2。这个时候,就须要在目标方法的入参处使用 @ModelAttribute,经过 value 属性来指定使用哪一个。
http://blog.sina.com.cn/s/blog_6d3c1ec601018cx1.html
@SessionAttribute 指的是 springmvc 的 session。向其中添加值得时候,同时会向 http session 中添加一条。在 sessionStatus.setComplete(); 的时候,会清空 sprinmvc
的 session,同时清除对应键的 http session 内容,可是经过,request.getSession.setAttribute() 方式添加的内容不会被清除掉。
其余状况下,springmvc session 和 http session使用状况相同。