举例说明
对于@modelattribute注解比较吃力。现在我们先考虑一个问题,关于数据的修改。
假设我们需要修改一个User对象,但是我们规定某一个或者几个字段不能修改,我们会利用表单填写相关信息,然后提交给后台,然后 在后台会new出一个对象,并将表单提交的数据赋值到这个表单的属性中,然后执行update操作,但是由于有些字段不能被修改,所以我们在前台传递的对象有些属性为空,所以在更新的时候,那些不能被修改的字段就会被默认为空值。
解决:1、我们可以利用隐藏域,在用户进行修改的时候,将对应的不能修改的字段进行隐藏,这样的话,可以解决,但是如果对应的字段的敏感度比较高,例如密码不适合利用隐藏域进行记录,这种方法是不合适的。
2、我们可以在数据更新之前,先从数据库中将用户信息给读取出来,然后将不能修改的字段重新进行赋值然后更新。但是如果所不能修改的字段比较多,我们就会对这个比较麻烦。重复代码较多。
引出:
对于上述两种方式都有对应的缺点,但是对于更新数据比较小的可以使用,所以我们可以利用我们的这个ModelAttribute注解进行处理。
注意 这里的对象不是new出来的,而是从数据库获取出来的,这就是ModelAttribute的作用之一。
在没有利用@ModelAttribute修饰前演示
<!-- POJO类ModelAttributes属性 -->
private int id;
private String name;
private int age;
private String email;
private String password; // 不能被修改
<!--
模拟修改操作 前台页面
1.原始数据为 1 tom 12 ff@163.com 12345
2.密码不能修改
3.表单回显 ,模拟操作直接在表单填写对应的属性值
在用户修改数据时,有些数据不能修改,所以在修改时我们把这些子段不给显示,
我们应该是从数据库中先获取,然后 利用ModelAttribute注解进行 覆盖,
会比我们自己手动利用覆盖的效果高
-->
<form action="TestModelArributes" method="post" >
<input type="hidden" name="id" value="1">
name:<input type="text" name="name" value="tom">
age:<input type="text" name="age" value="12" >
email:<input type="text" name="eamil" value="ff@163.com" >
<input type="submit" value="submit">
</form>
<!-- 后台处理 -->
@RequestMapping("/TestModelArributes")
public String TestModelArributes(User user){
System.out.println("修改:"+user);
// 执行更新操作 省略
return SUCCESS;
}
<!-- 控制台打印结果 -->
修改:User[id=1, name=tom, age=13, email=ff@163.com, password=null]
注意:在这里我们的原始数据 带有密码,但是从前台到后台处理的时候,这个密码由于没有在表单之中,所以传过来的POJO 的密码为空。即利用方法一所造成的。
然后我们在后台处理中新增加一个方法,注意是被@ModelAttribute修饰的。
@ModelAttribute
public void getUser(@RequestParam(value="id",required=false) Integer id,
Map<String, Object> maps){
if(id != null){ // id不为空,说明是用户执行了修改操作
// 模拟从数据库中获取对象 我们是生成的
User user =new User(1,"tom", 12,"ff@163.com", "1234");
System.out.println("从数据库中获取一个对象:"+user );
maps.put("user", user);
}
}
<!-- 控制台打印结果 -->
从数据库中获取一个对象:User[id=1, name=tom, age=12, email=ff@163.com, password=1234]
修改:User[id=1, name=tom, age=13, email=ff@163.com, password=1234]
当被@ModelAttribute修饰一个方法之后,结果就变成了password有值了,而且也应该注意到,被@ModelAttribute修饰的方法给提前执行了,这就是@ModelAttribute的作用。这样的话,再做修改操作的话,就能将原始密码给带进入修改操作了。为什么被@ModelAttribute修饰一个方法之后就会有这样的结果呢?
下面我们抽出主要代码进行分析
<!-- 第一种方式 -->
@ModelAttribute
public void getUser(@RequestParam(value="id",required=false) Integer id,
Map<String, Object> maps){
if(id != null){
User user =new User(1,"tom", 12,"ff@163.com", "1234");
maps.put("user", user);
}
}
@RequestMapping("/TestModelArributes")
public String TestModelArributes(User user){
System.out.println("修改:"+user);
return SUCCESS;
}
=============================
<!--第二种方式 -->
@ModelAttribute
public void getUser(@RequestParam(value="id",required=false) Integer id,
Map<String, Object> maps){
if(id != null){
User user=new User(1,"tom", 12, "1234");
maps.put("change", user);
}
@RequestMapping("/TestModelArributes")
public String TestModelArributes(@ModelAttribute( value="change") User user){
System.out.println("修改:"+user);
return SUCCESS;
}
注意
1、@ModelAttribute修饰的方法会被Springmvc提前执行,运行在每个目标方法之前。
2、在@ModelAttribute修饰的方法中,【map存入的键需要和目标方法入参类型的第一个字母小写的字符串一致】方式一所示,否则的话,在目标的请求参数方法中不能匹配到从数据库中提取出来的数据,或者就是你也将目标方法的参数用利用注解ModelAttribute修饰且设置value属性值指向你的map中的键名,上述代码的第二种方式
分析流程
运行流程:
我们上述代码可以分为三个步骤:
1、 执行ModelArribute修饰的方法,从数据库中取出对象,把对象放到Map 中,键为user。
2、springmvc 从Map 中取出User 对象 ,并把表单的请求参数赋给该User对象的对应属性。
3、springmvc 把上述对象传入到目标方法的参数。
@ModelAttribute源码分析流程
源码分析流程:对应上面的运行流程。
1、调用@ModelAttribute 注解修饰的方法,实际上把@ModelAttribute方法中 Map中的数据放在了implicitModel中。
2、解析请求处理器的目标参数,实际上该目标参数来自于WebDataBinder对象的target属性。
2.1、创建WebDataBinder对象。
a) 确定objectName属性:若传入的attrName属性值为空”“,则objectName为类名第一个字母小写。需要注意的是:attrName。如果目标方法的POJO参数属性使用了@ModelAttribute来修饰,则attrName值即为@ModelAttribute的value属性值(对应我们的第二种写法)
b)确定target属性:在implicitModel 中查找attrName对应的属性值,如果存在,进行利用。如果不存在,则验证当前Handler是否使用了@SessionAttribute进行修饰类。如果使用了@SessionAttribute,则尝试从HttpSession中获取attrName所对应的值,若Session中没有所对应的值,则会抛出异常。【这也是SessionAttribute与ModelAttribute共同使用时造成的一个常见问题】,如果当前Handler没有使用@SessionAttribute修饰类,且@SessionAttribute中没有使用value值指定的key和attrName相匹配,则通过反射机制进行创建POJO 对象。
2.2、Springmvc把表单的请求参数赋值给WebDataBinder 的target对应的属性。
2.3、Springmvc 会把WebDataBinder的attrName和target给到implicitModel,进而传递到request域对象中。
3、把WebDataBinder的target作为参数传递给目标方法。
POJO入参流程
从而我们也可以得出Springmvc进行传递POJO类型作为参数的流程:
1、确定一个key:
1.1、若目标方法的POJO类型的参数没有使用@ModelAttribute 作为修饰,则key为POJO类名第一个字母的小写。
1.2、如果使用了@ModelAttribute 来修饰,则key为@ModelAttribute 注解的value属性值。
2、在implicitModel中查找key对应的对象,如果存在则作为入参传入。
2.1、如在@ModelAttribute标记的方法中保存过,且key值与上述1中key一致,也可以进行获取(这就是上述代码为什么可以获取@ModelAttribute修饰的方法中map中的值)
3、若implicitModel 中不存在key对应的对象,则检查当前Handler是否使用了@SessionAttribute进行修饰类,如果使用了@SessionAttribute,且@SessionAttribute注解的value属性值中包含了key,则会从HttpSession中来获取key所对应的value值,若不存在则会抛出异常。
4、如果当前Handler没有使用@SessionAttribute修饰类,且@SessionAttribute中value属性值中不包含key,则通过反射机制进行创建POJO 对象。作为目标的方法参数对象。
5、Springmvc会把key和pojo类型的对象报错到implicitModel 中,进而保存到Request中。
使用总结
1、注解在没有返回值的方法上面
就如上面的例子注解在没有返回值的方法上面,用在POJO入参上面,或者我们可以在这里设定一些Request域中的对象,在前台可以直接使用。
@ModelAttribute
public void getUser(){
maps.put("name", "zhangsan");
}
2、注解在有返回值的方法上面,
这里要用value属性进行数据存储,会将返回值存储在value属性的值中,可以在前台直接获取。
@ModelAttribute(value="user00")
public User testModelAttribute(){
User user =new User(1,"tom00", 12,"ff@163.com", "123400");
return user;
}
3、在方法参数上使用@ModelAttribute
在方法的参数中使用必须是修饰POJO类,这样的话,才能从相应的域中获取数据
@RequestMapping("/TestModelArributes")
public String TestModelArributes(@ModelAttribute( value="change") User user){
System.out.println("打印"+user);
return SUCCESS;
}
这样的话,会从implicitModel 隐藏域中查询此类型对象,就犹如上面分析的传递POJO流程,其实这里的作用只是可以使用change标记我们的user对象。
4、尤其要注意ModelAttribute与SessionAttribute同时使用会产生的异常抛出,以及原因。
5、一定要理解POJO传递参数的流程
对于源码分析不理解的建议去看尚硅谷的佟刚的Springmvc教程,本实例就是根据他的视频写的笔记。