在这里强烈建议看看我之前写的几篇关于SpringMVC的博客,都是串通的。
@ModelAttribute这个是SpringMVC中处理模型数据的最难也是最重要的点。相当于以前Struct的拦截器。
用途:比如我们要修改一个对象的部分数据,按照以前的思维,new一个对象保存数据,然后赋值,把不修改数据先拿出来保存起来。但是这个已经Out了, 在SpringMVC中,是拿到数据库的实例,然后把传进来的值也就是需要修改的值set进去,那么没有set的值即为不需要修改的值。
index.jsp
<!-- 模拟修改操作
1. 原始数据 : 1, yexx, 123456, yexx@hust.com, 12
2. 密码不能被修改
3. 表单回显, 模拟操作直接在表单填写对应的属性值
-->
<form action="springmvc/testModelAttributes" method="post">
<input type="hidden" name="id" value="1"/>
username: <input type="text" name="username" value="yexx"/>
<br/>
email: <input type="text" name="email" value="yexx@hust.com"/>
<br/>
age: <input type="text" name="age" value="13"/>
<br/>
<input type="submit" value="Submit"/>
</form>
package com.hust.springmvc1;
import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.hust.springmvc.entities.User;
//@SessionAttributes(value={"user"}, types={String.class})
@Controller
@RequestMapping("/springmvc")
public class SpringMVCTest {
private static final String SUCCESS = "success";
/**
* 1. 由@ModelAttribute 标记的方法,会在每个目标方法执行之前被SpringMVC调用!
* 2. @ModelAttribute 注解也可以来修饰目标方法POJO 类型的入参,且value 属性值如下的作用:
* 1). SpringMVC 会使用value 属性值 在implicitModel 中查找对应的对象,若存在则会直接传入到目标方法的入参中。
* 2). SpringMVC 会一value 为 key , POJO 类型的对象为value, 存入request 中。
*/
@ModelAttribute
public void getUser(@RequestParam(value="id", required=false) Integer id,
Map<String, Object> map) {
System.out.println("ModelAttribute");
if (id!=null) {
//模拟从数据库中获取对象
User user = new User(1, "yexx", "123456", "yexx@hust.com", 12);
System.out.println("从数据库中获取一个对象:" + user);
map.put("user", user);
}
}
/**
* 运行流程:
* 1. 执行@ModelAttribute 注解修饰的方法:从数据库中取出对象,把对象放入到Map中。键为:user
* 2. SpringMVC 从 Map 中取出user对象, 并把表单的请求参数赋给该User 对象对应的属性。
* 3. SpringMVC 把上述对象传入目标方法的参数。
*
* 注意: 在@ModelAttribute 修饰的方法中,放入到Map时的键需要和目标方法入参类型的第一个字母小写的字符串一致
*
* SpringMVC 确定目标方法POJO 类型入参的过程
* 1. 确定一个key:
* 1). 若目标方法的POJO类型的参数木有使用@ModelAttribute 作为修饰,则key 为 POJO类名第一个字母的小写
* 2). 若使用了@ModelAttribute 来修饰, 则key 为@ModelAttribute 注解的value属性值。
* 2. 在 implicitModel 中查找 key 对应的对象, 若存在, 则作为入参传入
* 1). 若在@ModelAttribute 标记的方法中在 Map 中保存过, 且 key 和 1 确定的key 一致,则会获取到。
* 3. 若 implicitModel 中不存在key 对应的对象, 则检查当前的 Handler 是否使用了@SessionAttributes 注解修饰,
* 若使用了该注解, 且 @SessionAttributes 注解的 value 属性值中包含了key, 则会从HttpSession 中来获取 key 所对应
* 的value 的值, 若存在则直接传入到目标方法的入参中, 若不存在则将抛出异常。
* 4. 若Handler 没有表示@SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 则会
* 通过反射来创建 POJO类型的参数, 传入为目标方法的参数
* 5. SpringMVC 会把key 和 POJO 类型的对象 保存到implicitModel 中, 进而会保存到 request 中。
*
* 源代码分析的流程
* 1. 调用@ModelAttribute 注解修饰的方法。 实际上把@ModelAttribute 方法中Map 中的数据放在了 implicitModel中。
* 2. 解析请求处理器的目标参数,实际上该目标参数来自于 WebDataBinder 对象的 target属性
* 1). 创建WebDataBinder 对象
* ① 确定objectName 属性: 若传入的attrName 属性值为空,则objectName 为类名第一个字母小写。
* *注意: attrName 若目标方法的POJO属性使用了@ModelAttribute 来修饰,则attrName 值即为 @ModelAttribute的value值
* ② 确定target 属性:
* > 在implicitModel 中查找 attrName 对应的属性值。 若存在。 OK
* > 若不存在: 则验证当前Handler 是否使用了@sessionAttributes 进行修饰,若使用了,则尝试从Session中获取attrName 所对应
* 的属性值。 若session 中没有对应的属性值,则抛出异常
* > 若Handler 没有使用@SessionAttributes 进行修饰, 或@SessionAttribute 中没有使用 value 值制定的 key
* 和 attrName 相匹配,则通过反射创建POJO对象
*
* 2). SpringMVC 把表单的请求参数赋给了WebDataBinder 的target 属性
* 3). *SpringMVC 会把WebDataBinder 的attrName 和 target 给到 implicitModel
* 4). 把WebDataBinder 的 target 作为参数传递给目标方法入参。
*/
@RequestMapping("/testModelAttributes")
public String testModelAttributes(@ModelAttribute("user") User user) {
System.out.println("update:" + user);
return SUCCESS;
}
}
这段程序的运行结果就会是:
该回显数据也是能够显示出来。我把执行过程和源码分析拿出来特别的说一下。
运行流程:
* 1. 执行@ModelAttribute 注解修饰的方法:从数据库中取出对象,把对象放入到Map中。键为:user
* 2. SpringMVC 从 Map 中取出user对象, 并把表单的请求参数赋给该User 对象对应的属性。
* 3. SpringMVC 把上述对象传入目标方法的参数。
*
* 注意: 在@ModelAttribute 修饰的方法中,放入到Map时的键需要和目标方法入参类型的第一个字母小写的字符串一致。
源代码分析的流程
* 1. 调用@ModelAttribute 注解修饰的方法。 实际上把@ModelAttribute 方法中Map 中的数据放在了 implicitModel中。
* 2. 解析请求处理器的目标参数,实际上该目标参数来自于 WebDataBinder 对象的 target属性
* 1). 创建WebDataBinder 对象
* ① 确定objectName 属性: 若传入的attrName 属性值为空,则objectName 为类名第一个字母小写。
* *注意: attrName 若目标方法的POJO属性使用了@ModelAttribute 来修饰,则attrName 值即为 @ModelAttribute的value值
* ② 确定target 属性:
* > 在implicitModel 中查找 attrName 对应的属性值。 若存在。 OK
* > 若不存在: 则验证当前Handler 是否使用了@sessionAttributes 进行修饰,若使用了,则尝试从Session中获取attrName 所对应
* 的属性值。 若session 中没有对应的属性值,则抛出异常
* > 若Handler 没有使用@SessionAttributes 进行修饰, 或@SessionAttribute 中没有使用 value 值制定的 key
* 和 attrName 相匹配,则通过反射创建POJO对象
*
* 2). SpringMVC 把表单的请求参数赋给了WebDataBinder 的target 属性
* 3). *SpringMVC 会把WebDataBinder 的attrName 和 target 给到 implicitModel
* 4). 把WebDataBinder 的 target 作为参数传递给目标方法入参。
SpringMVC 确定目标方法POJO 类型入参的过程
* 1. 确定一个key:
* 1). 若目标方法的POJO类型的参数木有使用@ModelAttribute 作为修饰,则key 为 POJO类名第一个字母的小写
* 2). 若使用了@ModelAttribute 来修饰, 则key 为@ModelAttribute 注解的value属性值。
* 2. 在 implicitModel 中查找 key 对应的对象, 若存在, 则作为入参传入
* 1). 若在@ModelAttribute 标记的方法中在 Map 中保存过, 且 key 和 1 确定的key 一致,则会获取到。
* 3. 若 implicitModel 中不存在key 对应的对象, 则检查当前的 Handler 是否使用了@SessionAttributes 注解修饰,
* 若使用了该注解, 且 @SessionAttributes 注解的 value 属性值中包含了key, 则会从HttpSession 中来获取 key 所对应
* 的value 的值, 若存在则直接传入到目标方法的入参中, 若不存在则将抛出异常。
* 4. 若Handler 没有表示@SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 则会
* 通过反射来创建 POJO类型的参数, 传入为目标方法的参数
* 5. SpringMVC 会把key 和 POJO 类型的对象 保存到implicitModel 中, 进而会保存到 request 中。
总结
* 1. 由@ModelAttribute 标记的方法,会在每个目标方法执行之前被SpringMVC调用!
* 2. @ModelAttribute 注解也可以来修饰目标方法POJO 类型的入参,且value 属性值如下的作用:
* 1). SpringMVC 会使用value 属性值 在implicitModel 中查找对应的对象,若存在则会直接传入到目标方法的入参中。
* 2). SpringMVC 会一value 为 key , POJO 类型的对象为value, 存入request 中。
这里有一个特别值得注意的地方
如果你的Controller被@SessionAttributes修饰了,而且value也是那个,而且没用@ModelAttribute修饰方法,同时也没有@ModelAttribute修饰目标方法入参。这个时候就会抛出异常。我们知道原理之后很容易去避免这个异常。