文章目录
运行流程
- 执行
@ModelAttribute
注解修饰的方法:从数据库中取出对象,把对象放入到Map
中,键为:user
- SpringMVC从Map中取出
User
对象,并把表单的请求参数赋给该User
对象的对应的属性。 - SpringMVC把上述对象传入目标方法的参数。
注意:
1. 在@ModelAttribute
修饰的方法中,放入到Map
时的键需要和目标方法入参类型的第一个字母小写的字符串一致。
2. SpringMVC在调用目标方法之前,会逐个调用在方法上标注了@ModelAttribute
的方法。
代码
@ModelAttribute
注解修饰的方法:
@ModelAttribute
public void getUser(@RequestParam(value="id",required=false) Integer id,
Map<String, Object> map){
System.out.println("modelAttribute method");
if(id != null){
//模拟从数据库中获取对象
User user = new User(1, "Tom", "123456", "tom@atguigu.com", 12);
System.out.println("从数据库中获取一个对象: " + user);
map.put("user", user);
}
}
目标处理方法:
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user){
System.out.println("修改: " + user);
return SUCCESS;
}
源码分析流程
- 调用
@ModelAttribute
注解修饰的方法,实际上把@ModelAttribute
方法中Map
中的数据放在了implicitModel
中。 - 解析请求处理器的目标参数,实际上该目标参数来自于
WebDataBinder
对象的target
属性。
(1)创建WebDataBinder
对象:
①确定objectName
属性:若传入的attrName
属性值为“”,则objectName
为类名第一个字母小写。
注意:attrName
,若目标方法的POJO
属性使用了@ModelAttribute
来修饰,则attrName
值即为@ModelAttribute
的value
属性值。
②确定target
属性: - 在
implicitModel
中查找attrName
对应的属性值,若存在,ok。 - 若不存在:则验证当前
Handler
是否使用了@SessionAttributes
进行修饰,若使用了,则尝试从Session
中获取attrName
所对应的属性值,若session
中没有对应的属性值,则抛出了异常。 - 若
Handler
没有使用@SessionAttribute
进行修饰,或@SessionAttribute
中没有使用value
值指定的key
和attrName
相匹配,则通过反射创建了POJO对象。
(2)SpringMVC把表单的请求参数赋给了WebDataBinder
的target
对应的属性。
(3)SpringMVC会把WebDataBinder
的attrName
和target
给到implicitModel
。进而传到request
域对象中。
(4)把WebDataBinder
的target
作为参数传递给目标方法的入参。
结合代码分析
HandlerMethodInvoker.class
的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);
}
}
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);
//此时args中的参数还是在数据库中取的
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);
}
}
//解析请求处理器的目标参数,执行完之后args中的值进行了修改
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;
}
}
首先调用@ModelAttribute
注解修饰的方法,将在@ModelAttribute
中往Map
中放的键值对放入了invokeHandlerMethod
方法中的implicitModel
参数中,调用该方法时传入了参数implicitModel
:
解析请求处理器的目标参数:
resolveHandlerArguments
中的有用操作:创建binder
并为args
赋值,此时args
中的值为表单请求中的了。
WebDataBinder
的创建过程:
主要有两个属性:target
以及objectName
:
给binder
的两个属性target
以及objectName
赋值:
确定objectName
:
如果name
为空,获取类名的第一个字母的小写:name
值为user
。
注意:如果存在@ModelAttribute
注解,则attrName值即为@ModelAttribute
的value属性值。
确定target
:
- 先去
implicitModel
中去看,有没有name
对应的value
值,这里是有的,即为在数据库中获取的值。 - 不存在的话:若不存在:则验证当前
Handler
是否使用了@SessionAttributes
进行修饰,若使用了,则尝试从Session
中获取attrName
所对应的属性值,若session
中没有对应的属性值,则抛出了异常。 - 如果都不存在,就通过反射来创建一个对象。
此时binder
的target
为在数据库中获取的对象:SpringMVC调用doBind
方法将表单的请求参数赋值给binder
的target
属性。
为age
赋表单值,在doBind
中调用setAge()
方法:
此时args
变为已经被表单请求参数赋完值的从数据库中取的对象,
实际上该目标参数来自于WebDataBinder
对象的target
属性。
把WebDataBinder
的target
作为参数传递给目标方法的入参。
将当前binder
中的ObjectName
和target
赋值给implicitModel
:
implicitModel.putAll(binder.getBindingResult().getModel());
其中getModel
的具体操作:将当前binder
中的ObjectName
和target
返回:
SpringMVC确定目标方法POJO类型入参的过程
- 确定一个
key
(1)若目标方法的POJO类型的参数没有使用@ModelAttribute
作为修饰,则key
为POJO类名第一个字母的小写。
(2)若使用了@ModelAttribute
来修饰,则key
为@ModelAttribute
注解的value
属性值。 - 在
implicitModel
中查找key
对应的对象,若存在,则作为入参传入。
(1)若在@ModelAttribute
标记的方法中在Map
中保存过,且key
和1确定的key
一致,则会获取到。 - 若
implicitModel
中不存在key
对应的对象,则检查当前的Handler
是否使用@SessionAttributes
注解修饰,若使用了该注解,且@SessionAttributes
注解的value
属性值中包含了key
,则会从HttpSession
中来获取key
所对应的value
值,若存在则直接传入到目标方法的入参中,若不存在则将抛出异常。 - 若
Handler
没有标识@SessionAttributes
注解或@SessionAttributes
注解的value
值中不包含key
,则会通过反射来创建POJO类型的参数,传入为目标方法的参数。 - SpringMVC会把
key
和POJO类型的对象保存到implicitModel
中,进而会保存到request
中。