【1】场景需求与分析
背景需求如下,当更新一个对象时,某个字段比如密码不能被修改。
常见解决思路有如下
① new 一个对象,form表单中密码域为隐藏域,该种方法有风险。
② new 一个对象,在更新的时候再次从数据库查询密码从而进行更新,该方法比较麻烦。
SpringMVC的解决思路:使用@ModelAttribute
① JSP页面
<form action="springmvc/testModelAttribute" method="Post"> <input type="hidden" name="id" value="1"/> //注意此处隐藏域为id username: <input type="text" name="username" value="Tom"/> <br> email: <input type="text" name="email" value="tom@baidu.com"/> <br> age: <input type="text" name="age" value="18"/> <br> <input type="submit" value="Submit"/> </form>
② 后台代码
使用@ModelAttribute注解标注方法。
//该方法模拟根据id从数据库获取对象。 @ModelAttribute public void getUser(@RequestParam(value="id",required=false) Integer id, Map<String, Object> map){ System.out.println("modelAttribute method execute..."); if(id != null){ //模拟从数据库中获取对象 User user = new User(1, "Tom", "123456", "tom@baidu.com", 12); System.out.println("从数据库中获取一个对象: " + user); map.put("user", user); } } //根据表单name属性值对model中的user对象进行更新。因为表单域没有密码,故密码使用数据库查询得到的。 @RequestMapping("/testModelAttribute") public String testModelAttribute(@ModelAttribute("user") User user){ System.out.println("修改: " + user); return SUCCESS; }
测试结果
从数据库中获取一个对象: User [username=Tom, password=123456, email=tom@baidu.com, age=12, address=null] 修改: User [username=Tom, password=123456, email=tom@baidu.com, age=18, address=null]
对比可知,age进行了更新 ,password延用了数据库查询的数据。如下图所示,map中最终放的user并非数据库查询得到,而是经过了表单更新后的user。
【2】SpringMVC 确定目标方法 POJO 类型入参的过程
① 合并sessionAttributes中value不为null的属性到model中;
② 调用@ModelAttribute的方法;
③ 检测sessionAttributes存在标注了@ModelAttribute注解的方法参数但是值为null的情况,这种情况将会抛出异常
④ 确定目标方法参数的name(key)
若使用了 @ModelAttribute 且指定了value属性值如 @ModelAttribute("user") SysUser sysUser , 则 key 为 @ModelAttribute 注解的 value 属性值。
否则为若目标方法的 POJO 类型的名称如SysUser-sysUser;
⑤ 确定value
若model中存在key,则直接返回value;
若使用了@SessionAttributes注解且key-value存在不为空,则返回value(实际从session获取);
反射实例化一个空的pojo对象返回
⑥ 返回target和绑定结果(如果绑定过程出错,这个会有需要)
【3】@ModelAttribute运用实例
① @ModelAttribute注释void返回值的方法
@Controller public class HelloWorldController { @ModelAttribute public void populateModel(@RequestParam String abc, Model model) { model.addAttribute("attributeName", abc); //返回model } @RequestMapping(value = "/helloWorld") public String helloWorld() { return "helloWorld"; //返回的视图名 } }
这个例子,在获得请求/helloWorld 后,populateModel方法在helloWorld方法之前先被调用,它把请求参数(/helloWorld?abc=text)加入到一个名为attributeName的model属性中,在它执行后helloWorld被调用,返回视图名helloWorld和model(已由@ModelAttribute方法生产好了)。
② @ModelAttribute注释返回具体类的方法
@ModelAttribute public Account addAccount(@RequestParam String number) { return accountManager.findAccount(number); }
这种情况,model属性的名称没有指定,它由返回类型隐含表示,如这个方法返回Account类型,那么这个model属性的名称是account(默认为对象类型的首字母小写)。
③ @ModelAttribute(value="")注释返回具体类的方法
@Controller public class HelloWorldController { @ModelAttribute("abc") public String addAccount(@RequestParam String abc) { return abc; } @RequestMapping(value = "/helloWorld") public String helloWorld() { return "helloWorld"; //返回的视图名 } }
这个例子中使用@ModelAttribute注释的value属性,来指定model属性的名称。model属性对象就是方法的返回值(abc),它无须要特定的参数。
如果 @ModelAttribute(“abc”),然后方法里面使用了map.put(“user”,user)。那么,map额外会存在一个 key为abc,value为null的对象!其他方法如果绑定了 abc属性,那么将会获取到null!
④ @ModelAttribute和@RequestMapping同时注释一个方法
@Controller public class HelloWorldController { @RequestMapping(value = "/helloWorld.do") @ModelAttribute("attributeName") public String helloWorld() { return "hi"; } }
这时这个方法的返回值并不是表示一个视图名称,而是model属性的值,视图名称由RequestToViewNameTranslator根据请求"/helloWorld.do"转换为逻辑视图helloWorld。
Model属性名称有@ModelAttribute(value=””)指定,相当于在request中封装了key=attributeName,value=hi。
⑤ @ModelAttribute注释一个方法的参数
@Controller public class HelloWorldController { @ModelAttribute("user") public User addAccount() { return new User("jz","123"); } @RequestMapping(value = "/helloWorld") public String helloWorld(@ModelAttribute("user") User user) { user.setUserName("jizhou"); return "helloWorld"; } }
在这个例子里,@ModelAttribute("user") User user注释方法参数,参数user的值来源于addAccount()方法中的model属性。
因为@ModelAttribute 注解的方法总会在其他方法调用前执行。
此时如果类没有标注@SessionAttributes("user"),那么scope为request,如果标注了,那么scope为session。如果controller上标注了@SessionAttributes("user"),那么如果从model中获取不到,就会从session中尝试获取。如果仍旧获取不到,则会抛出异常!