Spring:深入分析SpringMVC之数据处理模型

对于MVC框架来说,模型数据是最重要的,因为控制是为了产生模型数据,而试图是为了渲染模型数据。

我们都知道SpringMVC通过@RequestMapping将请求引导到处理方法上,使用合适的方法签名将请求消息绑定到入参中。方法入参绑定请求消息只是处理方法的第一步,还有更重要的任务等待完成,即:根据入参执行的相关逻辑,产生模型数据,导向到特定视图中。

将模型数据暴露给视图是SpringMVC的一项重要工作。SpringMVC提供了多种途径输出模型数据:

[1] ModelAndView:当处理方法返回值类型为ModelAndView时,方法体即可通过该对象添加模型数据。

[2] @ModelAttribute:在方法入参标注该注解后,入参的对象就会放到数据模型中。

[3] Map及Model:如果方法入参为org.springframework.ui.Model、org.springframework.ui.ModelMap或java.util.Map,则当处理方法返回时,Map中的数据会自动添加到模型中。

[4] @SessionAttributes:将模型中的某个属性暂存到HttpSession中,以便多个请求之间可以共享这个属性。

1 ModelAndView

控制器处理方法的返回值如果未ModelAndView,则其既包含视图信息,又包含模型数据信息,这样SpringMVC就可以使用视图对模型数据进行渲染了。可以简单的将模型数据看成一个Map<String,Object>对象。

在处理方法的方法体中,可以使用如下方法添加模型数据:

[1] ModelAndView addObject(String attributeName,Object attributeValue)。

[2] ModelAndView addAllObjects(Map<String,?> modelMap)。

可以通过如下方法设置视图:

[1] void setView(View view):指定一个具体的视图对象。

[2] void setViewName(String viewName):指定一个逻辑视图名。

2 @ModelAttribute

如果希望将方法入参对象添加到模型中,则仅需在响应入参前使用@ModelAttrivute注解即可。

@RequestMapping(path = "/updateUser")
public String updateUser(@ModelAttribute("user")User user){
	return "/user/list";
}

SpringMVC将请求消息绑定到User对象中,然后再以user为键将User对象放到模型中。在准备视图进行渲染前,SpringMVC还会进一步将模型中的数据转储到视图的上下文中并暴露给视图对象。对于jsp视图来说,SpringMVC会将模型数据转储到ServletRequest的属性列表中,通过ServletRequest的setAttribute(String name,Object o)方法保存。

除了可以在方法入参上使用@ModelAttribute注解外,还可以在方法定义中使用@ModelAttribute注解的方法,并将这些方法的返回值添加到模型中。下面是在方法级上使用@ModelAttribute注解的demo:

/**
* 请求访问UserController中的任何一个请求处理前,SpringMVC先执行该方法,并将返回值以user为键添加到模型中
*/
@ModelAttribute("user")
public User getUser(){
	return new User();
}
/**
*  在此,模型数据会付给User的入参,在根据HTTP请求消息进一步填充覆盖user对象
*/
@RequestMapping(value="/updateUser")
public String updateUser(@ModelAttribute("user")User user){
	user.setUserName("aa");
	return "/user/list";
}

在访问UserController中的任何一个请求处理方法前,都会实现执行标注了@ModelAttribute的getUser()方法,并将其返回值以user为键添加到模型中。

由于上面例子中第一个方法与第二个方法注解上的key值相同,SpringMVC会将 1 处获取的模型属性先复制给 2 处的入参user,然后在根据HTTP请求消息对user的属性进行填充覆盖,得到一个整合版的user对象。

注意:处理方法入参对一个参数只能使用一个注解,如:使用了@ModelAttribute注解就不能在使用@RequestParam或@CookieValue。如果使用多个则会抛出异常。

3 Map及Model

SpringMVC在内部使用一个org.springframework.ui.Model接口存储模型数据,它的功能类似于java.util.Map,但它比Map易用。org.springframework.uiModelMap实现了Map接口,而org.springframework.ui.ExtendedModelMap扩展于ModelMap的同时实现了Model接口。

SpringMVC在调用方法前会创建一个隐含的模型对象,作为模型数据的存储容器,我们称为隐含模型。如果处理方法的入参为Map或Model类型,则SpringMVC会将隐含模型的引用传递给这些入参。在方法体内,开发者可以通过这个入参对象访问到模型中的所有数据,也可以像模型中添加新的属性数据。

@ModelAttribute("user")
public User getUser(){
	return new User();
}
/**
*  SpringMVC将请求对应的隐含模型对象传递给modelMap,因此在方法中可以通过它访问模型中的数据。
*/
@RequestMapping(value="/updateUser")
public String updateUser(ModelMap modelMap){
	modelMap.addAttribute("test","value");
	User user == (User)modelMap.get("user");
	user.setUserName("xiaohei");
	return "/user/list";
}

SpringMVC一旦发现处理方法有Map或Model类型的入参,就会将请求内在的隐含模型对象传递给这些参数,因此在方法体重可以通过这个入参对模型中的数据进行读/写操作。

4 @SessionAttributes

如果希望在多个请求之间公用某个模型属性数据,则可以在控制器类中标注一个@SessionAttributes,SpringMVC会将模型中对应的属性暂存到HttpSession中:

@Controller
@RequestMapping("/userController")
@SessionAttribute("user")//将@ModelAttribute("user")所标注的对象自动保存到HttpSession中
public class UserController{
	/**
	*  SpringMVC将请求对应的隐含模型对象传递给modelMap,因此在方法中可以通过它访问模型中的数据。
	*/
	@RequestMapping(value="/updateUser")
	public String updateUser(@ModelAttribute("user")User user){
		user.setUserName("xiaohei");
		return "/user/list";
	}

	@RequestMapping(value="/getUser")
	public String getUser(ModelMap modelMap,SessionStatus sessionStatus){
		User user == (User)modelMap.get("user");//读取模型中的数据
		if(user != null){
			user.setUserName("xiaohei");
			sessionStatus.setComplete();//清除本处理器对应的会话属性
		}
		return "/user/showUser";
	}
}

很可惜,当启动Web应用并向updateUser发送请求时,SpringMVC会抛出一下异常信息:

session attribute ‘user’ required - not found in session

原因是SpringMVC对@ModelAttribute及@SessionAttributes的处理遵循一个特定的流程,当流程条件不满足时就会报错。这个处理流程简单说明如下:

① SpringMVC在调用处理方法前,在请求线程中自动创建一个隐含的模型对象。

② 调用所有标注了@ModelAttribute的方法,并将方法返回值添加到隐含模型中。

③ 查看Session中是否存在@SessionAttributes("xx")属性所指的的xx属性,如果有则将其添加到隐含模型中。如果隐含模型中已经有xx属性,则该步骤操作会覆盖模型中已有的属性值。

④ 对标注了@ModelAttribute("xx")处理方法的入参按如下流程处理:

[1] 如果隐含模型拥有名为xx的属性,则将其赋给该入参,在用请求消息填充该入参对象,然后直接返回,否则继续。

[2] 如果xx是会话属性,即在处理类定义处标注了@SessionAttributes("xx"),则尝试从会话中获取该属性,并将其赋给该入参,然后在用请求消息填充该入参对象。如果在会话中找不到对应的属性,则抛出HttpSessionRequiredException异常,否则继续。

[3] 如果隐含模型中不存在xx属性,且xx也不是会话属性,则创建入参的对象实例,然后在用请求消息填充该入参。

分析上述代码,由于标注了@SessionAttributes("user"),因此user为会话属性,SpringMVC在对@ModelAttribute("user")进行处理时,会先在隐含模型中查询是否有对应的属性,如果不存在,则继续在会话中查询该属性。由于在会话中也不存在该属性,因此抛出异常。

解决该异常的方法很简单,只需添加一个方法,以便让SpringMVC在处理@ModelAttribute标注前先想隐含模型中添加一个user属性,这个[1]会执行并返回,就可以避免抛出异常。

@ModelAttribute("user")
public User getUser(){
	return new User();
}

@SessionAttribute 除了可以通过属性名指定需要放到会话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中。如@SessionAttributes(types=User.class)会将隐含模型中所有类型为User.class的属性添加到会话中。

此外,@SessionAttributes还可以通过属性名及types一起指定,二者都允许多值,放到会话中的属性是二者的冰机。如一下的声明方式都是合法的:

[1] @SessionAttributes(value={"user1","user2"}):将名为user1及user2的模型属性添加到会话中。

[2] @SessionAttributes(types={User.class,Dept.class}):将模型中所有类型为User及Dept的属性添加到会话中。

[3] @SessionAttributes(value={"user1","user2"},types={Dept.class}):将名为user1和user2的及类型为Dept的模型属性添加到会话中。

发布了110 篇原创文章 · 获赞 475 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/yongqi_wang/article/details/87349348