在实际开发中可能遇到这样一种开发场景:(本文出自http://my.oschina.net/happyBKs/blog/422513)
我们的一些model对象里包含了一些属性,对应到显示view页面的表单上的项是不存在的,如一些敏感数据,像密码,或者id什么的。如果new一个对下昂更新数据时,按照表单的数据进行更新,一些属性就为空了。你也许听得有些莫名其妙?“那就从数据库中取出对应的记录,并取出相应的字段值补到model的这个属性上不就可以了,这也算问题”。是的,但是这样做不够“优雅”、“高大上”、“装B”。
理想的情况应该怎么样呢?
我们希望对于表单数据构造出的对象,数据库中相应的对象的属性会对应的填充进去。是不是有点绕?这么说吧,想象一下,如果这个对象特别的大,特别的大,特别的大,特别的大,特别的大,特别的大,特别的大,特别的大,特别的大,特别的大。。。。。。属性特别的多,那么你如果手动比较对照那些属性是表单中没有的(这些属性可能造成更新对象的相应的属性为空),那么就需要找出它们并注意赋值,这是多low的一件事情啊!麻烦且容易出错。
现在springMVC来了,当当当当当。。。。。
他只需要你从数据库把对象取出,剩下的那些操作他来完成。那么这种智能来源于哪呢?就是我们今天要介绍的模型数据篇的最后一个专题——@ModelAttribute注解。
@ModelAttribute
• 在方法定义上使用 @ModelAttribute 注解:Spring MVC在调用目标处理方法前,会先逐个调用在方法级上标注了@ModelAttribute 的方法。
• 在方法的入参前使用 @ModelAttribute 注解:
– 可以从隐含对象中获取隐含的模型数据中获取对象,再将请求参数绑定到对象中,再传入入参
– 将方法入参对象添加到模型中
还是一头雾水吗?看个例子和代码吧:
对象类的定义如下:indexModelAttribute.jsp
package com.happyBKs.springmvc.beans; public class UserBean { @Override public String toString() { return "UserBean [id=" + id + ", username=" + username + ", password=" + password + ", age=" + age + "]"; } public UserBean(int id, String username, String password, int age) { super(); this.id = id; this.username = username; this.password = password; this.age = age; } public UserBean() { super(); } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } Integer id; String username; String password; int age; }
请求页面:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> <form action="ModelAttribute/userma" method="post"> <input type="hidden" name="id" value="1" /> <br/> <input type="text" name="username" value="happyBKs" /> <br/> <input type="text" name="age" value="12" /> <br/> <input type="submit" value="submit"> <br/> </form> </body> </html>
控制器类描写如下:
package com.happyBKs.springmvc.handlers; 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.happyBKs.springmvc.beans.UserBean; @RequestMapping("/ModelAttribute") @Controller public class ModelAttributeHandler { @RequestMapping("/userma") public String handle(UserBean user) { String returnStr="modelAttributeTest1"; System.out.println("updating "+user); return returnStr; } /* * 带有@ModelAttribute注解标记的方法,会在每个目标方法执行之前被SpringMVC调用 * * 运行过程: * 1. 执行@ModelAttribute注解修饰的方法:从数据库取出对象,把对象放入Map中。键为userBean * 2. SpringMVC从Map中取出UserBean对象,并发表单中的参数赋给UserBean对象对应的属性 * 3. SpringMVC把上述对象传入目标方法参数 * * 注意:在@ModelAttribute方法中,放入Map的键必须和入参类型的第一个字母小写的字符串一致。 * */ @ModelAttribute public void getUser(@RequestParam(value="id",required=false)Integer id, Map<String,Object> map) { System.out.println("calling @ModelAttribute getUser"); if(id!=null)//注意:如果是int id就不能够用==null来判断了 { UserBean user=new UserBean(1,"happyBKs","12345678",200); System.out.println("从数据库中获取对象:"+user); map.put("userBean", user); } } }
这里需要注意的是,在请求url时,springMVC的执行顺序是:先执行标记了映射到的控制器类的带有 @ModelAttribute注解的方法进行执行,生成相应的类的对象,然后用 @ModelAttribute注解方法的Map类型的参数存放这个模型数据对象;之后springMVC再映射到相应的处理目标方法上,目标方法的参数包含一个 @ModelAttribute注解方法存放于Map参数中的对象,并且,注意了!!!!!,在@ModelAttribute方法中,放入Map的键必须和入参类型的第一个字母小写的字符串一致。比如说如果这里@ModelAttribute方法中map.put("userBean", user);改成map.put("UserBean", user);,执行结果就是虽然@ModelAttribute方法getUser()被执行了,但是目标方法中输出的模型数据对象向的password仍是空,也就是不能将数据库中的字段值补上去。
这里需要解释一下:
UserBean user=new UserBean(1,"happyBKs","12345678",200); System.out.println("从数据库中获取对象:"+user);
这两行代码模拟从数据库中取出一个完整对象。真正情况可以替换成调用持久层模块的代码。
还有,为了验证执行顺序,我在getUser()中加了一个
System.out.println("calling @ModelAttribute getUser");
为的是看看是否@ModelAttribute方法会先于目标方法执行。
这里我在对结论做一个扩展:springMVC在映射请求的过程中,在映射到控制器类之后,会先执行带@ModelAttribute的方法,无论你实际映射的处理函数的输出模型数据是否与这个@ModelAttribute方法有关,也就是说,如果你在现在这个控制器类中加入其它处理目标方法,并且与当前类对象和表单没有任何关系,在映射时,@ModelAttribute方法getUser都会被执行。
好,现在我们来看看运行情况:
输出结果如下:
calling @ModelAttribute getUser 从数据库中获取对象:UserBean [id=1, username=happyBKs, password=12345678, age=200] updating UserBean [id=1, username=happyBKs, password=12345678, age=12]
看到了吧,如果我并没有在代码中可以的将原始对象从数据库中取出并挑选出表单对象中没有覆盖到的属性并赋值,这里指的是password,表单中没有password,但类中定义了,数据库中有,那么让数据库的对象被整个取出,由springMVC来帮助我们完成那些繁琐且易错的工作。
这里像杜邦杜庞兄弟那样,我再强调一遍:“在@ModelAttribute方法中,放入Map的键必须和入参类型的第一个字母小写的字符串一致。”否则结果如下,password没有被成功赋予数据库中对应的值:
calling @ModelAttribute getUser 从数据库中获取对象:UserBean [id=1, username=happyBKs, password=12345678, age=200] updating UserBean [id=1, username=happyBKs, password=null, age=12]
至此,模型数据篇就介绍完了。
_______________________________________________________________-
补充,这里有个重要的点忘记说了,补充一下。ModelAttribute方法向Map参数中存放的键值对的键的名称虽然在默认情况需要与目标方法的参数对象的类型名称的首字母小写的单词对应,但是也可以用户自己指定ModelAttribute方法存入map参数的键的名称,即目标方法取对象的键的名称。
方法就是在目标方法的参数对象前面加入一个标识:@ModelAttribute(value="mauser")
package com.happyBKs.springmvc.handlers; 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.happyBKs.springmvc.beans.UserBean; @RequestMapping("/ModelAttribute") @Controller public class ModelAttributeHandler { @RequestMapping("/userma") public String handle(@ModelAttribute(value="mauser") UserBean user) { String returnStr="modelAttributeTest1"; System.out.println("updating "+user); return returnStr; } /* * 带有@ModelAttribute注解标记的方法,会在每个目标方法执行之前被SpringMVC调用 * * 运行过程: * 1. 执行@ModelAttribute注解修饰的方法:从数据库取出对象,把对象放入Map中。键为userBean * 2. SpringMVC从Map中取出UserBean对象,并发表单中的参数赋给UserBean对象对应的属性 * 3. SpringMVC把上述对象传入目标方法参数 * * 注意:在@ModelAttribute方法中,放入Map的键必须和入参类型的第一个字母小写的字符串一致。 * */ @ModelAttribute public void getUser(@RequestParam(value="id",required=false)Integer id, Map<String,Object> map) { System.out.println("calling @ModelAttribute getUser"); if(id!=null)//注意:如果是int id就不能够用==null来判断了 { UserBean user=new UserBean(1,"happyBKs","12345678",200); System.out.println("从数据库中获取对象:"+user); map.put("mauser", user); } } }
运行的结果也是成功的。
calling @ModelAttribute getUser
从数据库中获取对象:UserBean [id=1, username=happyBKs, password=12345678, age=200]
updating UserBean [id=1, username=happyBKs, password=12345678, age=12]
如果我们在目标页面写EL表达式,应该使用的是mauser来获取对象:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ page isELIgnored="false"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Model Attribute</title> ${requestScope.mauser} </head> <body> </body> </html>