在SpringMVC中处理模型数据的具体实现代码下载地址:http://download.youkuaiyun.com/download/bingbeichen/9793370。
SpringMVC主要提供了以下几种途径来处理模型数据,即:
- ModelAndView:目标处理器中处理方法的返回值类型为ModelAndView时, 方法体即可通过该对象添加模型数据;
- Map及Model:入参为org.springframework.ui.Model、org.springframework.ui.ModelMap或java.uti.Map时,目标处理器中处理方法返回时,Map中的数据会自动添加到模型中;
- @SessionAttributes:将模型中的某个属性暂存到HttpSession中,以便多个请求之间可以共享该属性;
- @ModelAttribute:目标处理器中处理方法的入参标注该注解后,入参的对象即会放到数据模型中。
1. ModelAndView
在目标处理器的处理方法中,若返回值为ModelAndView类型,则其既包含视图信息又包含模型数据信息。
@RequestMapping("/processModelData")
@Controller
public class ProcessModelData {
private static final String SUCCESS = "success";
/**
* 目标方法的返回值可以是ModelAndView类型,其可以包含视图和模型数据
* 原理:SpringMVC会将ModelAndView的model中的数据存放到request域对象中
* @return
*/
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() {
ModelAndView modelAndView = new ModelAndView(SUCCESS);
// 目标页面中可以直接用“${requestScope.time}”来获取所存放的模型数据
modelAndView.addObject("time", new Date());
return modelAndView;
}
}
2. Map及Model
Spring MVC在内部使用org.springframework.ui.Model接口来存储模型数据。即Spring MVC在调用目标方法前会创建一个隐含的模型对象作为模型数据的存储容器;如果方法的入参为Map或Model类型,Spring MVC会将隐含模型的引用传递给该入参。因此,在方法体内,开发者即可以通过该入参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据。
@RequestMapping("/processModelData")
@Controller
public class ProcessModelData {
private static final String SUCCESS = "success";
/**
* 目标方法可以添加Map类型的参数,实际上也可以是Model或ModelMap类型
* @param map
* @return
*/
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map) {
System.out.println(map.getClass().getName()); // BindingAwareModelMap
// 在目标页面可以通过“${requestScope.names}”的方式来访问模型数据
map.put("names", Arrays.asList("qiaobc", "qiaob", "qiaodb"));
return SUCCESS;
}
}
3. @SessionAttributes注解
若希望在多个请求之间共用某个模型属性数据,则可以在控制器类上标注一个@SessionAttributes注解, Spring MVC将把模型中对应的属性暂存到HttpSession中。
@SessionAttributes(value={"username"}, types={Date.class})
@RequestMapping("/processModelData")
@Controller
public class ProcessModelData {
private static final String SUCCESS = "success";
/**
* @SessionAttributes注解:
* 1). 既可以通过属性名指定需要放到会话中的属性(实际上使用的是value属性值)
* 2). 也可以通过属性类型指定要放到会话中的属性(实际上使用的是types属性值)
* 3). 注意:该注解只可以标记在类上,而不能标记在方法上
* @param map
* @return
*/
@RequestMapping("/testSessionAttributes")
public String testSessionAttributes(Map<String, Object> map) {
map.put("username", "whut-qiaobc");
map.put("birth", new Date());
return SUCCESS;
}
}
4. @ModelAttribute注解
4.1 使用场景
在更新数据表记录且记录中存在不能修改的字段时,会先在目标方法中新建一个对应类的对象,表单会将修改后的字段值传递给该对象的指定属性,而不能修改字段的属性值却为空;此时,可在form表单中添加隐藏域(缺点:不便维护、可能泄露隐私)或从数据库中重新查找出该字段值。
在SpringMVC中即可先从数据库中获取模型数据,再根据表单参数修改获取的模型数据,进而进行更新操作,具体如下图所示:
4.2 具体实现
//@SessionAttributes(value={"username"}, types={Date.class})
@RequestMapping("/processModelData")
@Controller
public class ProcessModelData {
private static final String SUCCESS = "success";
/**
* 1. @ModelAttribute注解标记的方法会在每个目标方法执行之前执行
* 2. @ModelAttribute注解也可以修饰目标方法POJO类型的入参,其value属性值有如下作用:
* > SpringMVC会使用value属性值在implicitModel中查找对应的对象,若存在则直接入参
* > SpringMVC会以value为key、POJO类型的对象为value,放入request域对象中
* @param id
* @param map
*/
@ModelAttribute
public void getUserModel(@RequestParam(value="id", required=false) Integer id, Map<String, Object> map) {
if(id != null) {
// 模拟从数据库获取记录
User user = new User(1, "whut-qiaobc", "qiaobc@whut.edu.cn", "123456", 23);
System.out.println("修改前:" + user);
// 将记录放入request请求域中
map.put("user", user);
}
System.out.println("@ModelAttribute");
}
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user) {
System.out.println("修改后:" + user);
return SUCCESS;
}
}
<!-- 模拟修改数据库记录 -->
<form action="processModelData/testModelAttribute" method="post">
<input type="hidden" name="id" value="1">
username:<input type="text" name="username" value="whut-qiaobc">
<br><br>
email:<input type="text" name="email" value="qiaobc@whut.edu.cn">
<br><br>
age:<input type="text" name="age" value="23">
<br><br>
<input type="submit" value="submit">
</form>
注意:在使用SpringMVC进行数据绑定时,要主要目标控制器中处理方法的参数名与请求页面中的参数名相同,否则会出现“The request sent by the client was syntactically incorrect().”错误,其实质即是SpringMVC在进行参数绑定时出错。
4.3 运行流程
/**
* 运行流程:
* 1. 执行@ModelAttribute注解修饰的方法,即从数据库获取对象,并将该对象放入Map中(键为user)
* 2. SpringMVC从Map中取出User对象,并将表单的请求参数赋给该User对象对应的属性
* 3. SpringMVC将User对象传递给目标方法的入参
*
* 特别注意:在@ModelAttribute标记的方法中,放入Map的键应与目标方法入参类型的小写字符串一致
*
* 源代码分析的运行流程:
* 1. 调用@ModelAttribute注解修饰的方法,实际上是将该方法Map中的数据放入到implicitModel中
* 2. 解析请求处理器的目标参数,实际上该目标参数来自于WebDataBinder对象的target属性
* 1). 创建WebDataBinder对象
* a). 确定ObjectName属性:
* > 若传入的attrName属性值为"",则objectName为目标方法的入参类型名称的第一个字母小写
* * 注意:若目标方法的POJO属性使用@ModelAttribute注解修饰,则attrName为其value属性值
* b). 确定target属性:
* > 在implicitModel中查找attrName对应的属性值,存在则返回;
* > 若不存在,则验证当前处理器是否使用@sessionAttributes注解修饰:
* > 若使用,且其value值指定的key与attrName相匹配 或 其types值指定的类型与目标方法的入参类型相匹配
* > 则尝试从Session中获取attrName对应的属性值,若获取不到则抛出异常
* > * 否则,则根据目标方法的参数类型以反射的方式创建POJO对象。
* 2). SpringMVC调用doBind()方法将表单的请求参数赋给WebDataBinder的target对应的属性
* 3). * SpringMVC将WebDataBinder的attrName和target属性赋给implicitModel,进而传到request域对象中
* 3. 将WebDataBinder的target作为参数传递给目标处理器处理方法的入参
*
*/
4.4 确定目标方法POJO类型参数的入参过程
/**
* 1. 确定key
* 1). 若目标方法的POJO类型参数使用@ModelAttribute修饰,则key为其value属性值
* 2). 若未使用则key为目标方法POJO类型名称的第一个字母小写
* 2. 先在implicitModel中查找key对应的对象,若存在则作为入参传入
* 若在@ModelAttribute修饰的方法中向Map中保存过,且key与上述key相同则可获取到
* 3. 若在implicitModel中不存在key对应的对象,则检查当前handler是否标记了@SessionAttributes注解
* 1). 若使用,且注解的value属性值包含key,则尝试从HttpSession中获取key对应的属性
* 2). 若存在则直接传入目标方法的入参中,若不存在则抛出异常
* 4. 若当前handler未标记@SessionAttributes注解 或 该注解的value属性值不包含key,则通过反射创建POJO并入参
* 5. SpringMVC将key和POJO类型的对象保存到implicitModel中,进而保存到request域对象中
*/
4.5 避免@SessionAttributes所引发的异常
根据运行流程分析可知,当标记类的注解@SessionAttributes(value={"user"}, types={Date.class})与目标方法public String testModelAttribute(User user){}相遇,且implicitModel中没有user对应的属性时,则SpringMVC会尝试从会话中获取user对应的属性,若获取失败则会抛出HttpSessionRequiredException异常。
本文介绍了SpringMVC中处理模型数据的四种方法:使用ModelAndView、Map或Model、@SessionAttributes和@ModelAttribute,详细解释了每种方法的工作原理及应用场景。
9万+

被折叠的 条评论
为什么被折叠?



