Spring MVC 数据模型(下)

1. 前言

Spring MVC 为了解决 HTTP 协议的无状态性,提供了很多能适用于不同作用域需求的模型对象,从而保证程序中的数据能在开发者需要的地方出现。

本节课继续和大家聊聊 Spring MVC 中的数据模型组件。

  • Model;
  • ModelMap;
  • ModelAndView。

2. 更多数据模型

模型是一个用的很广的概念。本课程中数据模型应该有 2 层含义:

  • WEB 程序中封装数据的组件;
  • 此模型对象会有自己特定作用域。

上一小节中,向大家介绍了 Map 模型对象,其本质是对 HttpServletRequest 对象中的存储功能的引用。Map 还是比较原始和天然的。Spring MVC 为了展现自己的特色服务,对 Map 进行了多层面的封装,提供了更丰富的数据模型对象。

Tips: 重要的事情强调一下,你将认识或使用到的数据模型会很多,但其本质是:

  • 如果是请求作用域,你只是在间接使用 HttpServletRequest 对象;
  • 如果是会话作用域,你只是在间接使用 HttpSession 对象。

2.1 Model

这个字面意思很明显,咱就是一个模型组件。

Model 是一个接口类型,Model 接口中提供了标准的保存数据的方法。

Model addAttribute(String attributeName, @Nullable Object attributeValue);

Model 使用起来很简单,将 Model 设为控制器方法的参数便可 ,其它的交给 Spring MVC,因为 Model 是一个接口类型,Spring MVC 会为我们辨明身份,注入一个具体的实例对象。

@RequestMapping(value="/login",method=RequestMethod.POST)
public String login01(User user,Model map) {
	if("mk".equals(user.getUserName()) && "123".equals(user.getUserPassword())) {
		map.addAttribute("loginUser", user);
		return "index";
	}
	return "fail";
}

Model 接口中还有其它的方法,但都不常用,感兴趣的话大家可以查阅其源代码或 API 文档。

2.2 ModelMap

这个组件的字面意思是说,我是用 Map 存储数据的模型。可以查阅一下其源代码结构.,本质上还就是一个链表实现的 Map,都是一家人呀。

Map、Map …… 又见 Map。

ModelMap 中提供的方法和 Model 接口差不多,不同之处在于有自己的实现。

public class ModelMap extends LinkedHashMap<String, Object> {
	public ModelMap addAttribute(String attributeName, @Nullable Object attributeValue) {
		Assert.notNull(attributeName, "Model attribute name must not be null");
		put(attributeName, attributeValue);
		return this;
	} 
}

如何使用?

Spring MVC 的世界,会让你感觉到自己的多余,因为很多事情 Spring MVC 都帮你温柔的解决。一样的,只需要设定为控制器方法的参数就可以了。

@RequestMapping(value="/login",method=RequestMethod.POST)
public String login01(User user,ModelMap map) {	
	if("mk".equals(user.getUserName()) && "123".equals(user.getUserPassword())) {
		map.addAttribute("loginUser", user);
		return "index";
	}
	return "fail";
}

会发现,大家除了名字不一样外,其它好像也没有什么不一样。

2.3 ModelAndView

从字面上了解这个组件,应该是除了充当模型的功能外,还与视图有关系。想要彻底看透它,很简单,查阅一下源代码结构。

public class ModelAndView {
@Nullable
private Object view;
@Nullable
private ModelMap model;
//……
}

图片描述

Tips: 搞了半天,会发现,ModelAndView 其实就是封装了 ModelMap 外加一个用来保存视图相关信息的 view 对象。

其实,大家都是一家人,根源是相同的,具有共同的遗传基因,当然,还会有只属于自己的独有功能。

ModelAndView 和其它的数据模型使用起来稍有一点不同,往往是作为控制器方法的返回值。

@RequestMapping(value="/login",method=RequestMethod.POST)
public ModelAndView login01(User user) {
	ModelAndView mv=new ModelAndView();
	if("mk".equals(user.getUserName()) && "123".equals(user.getUserPassword())) {
		mv.addObject("loginUser", user);
		mv.setViewName("index");
		return mv;
	}
	mv.setViewName("fail");
	return mv;
}

Tips: ModelAndView 把数据模型和视图拼在了一起,便于一次性交给前端控制器处理。

3. 会话作用域

Map、Model 、ModelMap、ModelAndView 这几个数据模型组件,默认情况下,其中所保存的数据都是请求作用域级别的。

在很多应用场景下,需要数据在整个会话过程中都能访问到。比如登录者信息、购物车信息等。

面对这种数据需求时,Spring MVC 又如何实现?

你能想到的,Spring MVC 早就想到了。使用 @SessionAttributes 注解即可。

@SessionAttributes 注解是类级别的注解,需要添加在控制器类的前面。

@Controller
@RequestMapping("/user")
@SessionAttributes("loginUser")
public class UserAction {
@RequestMapping(value="/login",method=RequestMethod.POST)
	public ModelAndView login01(User user) {
	ModelAndView mv=new ModelAndView();
	if("mk".equals(user.getUserName()) && "123".equals(user.getUserPassword())) {
		mv.addObject("loginUser", user);
		mv.setViewName("index");
		return mv;
	}
	mv.setViewName("fail");
	return mv;
}
}

Tips: @SessionAttributes(“loginUser”) 中的属性名 loginUser 必须保持和 mv.addObject(“loginUser”, user); 中的 loginUser 名一样。

从底层思维来讲, Spring MVC 即把数据保存到请求作用域中、也保存到会话作用域中。

测试时,只需要在 index.jsp 中添加如下面的 EL 表达式,指明数据的作用域。

<body>
 我是首页
 <br/>
 请求作用域中得到当前登录者:${requestScope.loginUser.userName}
 <br/>
 会话作用域中得到当前登录者:${sessionScope.loginUser.userName}
</body>

启动浏览器,打开登录页面,输入登录名、登录密码、点击登录,然后在浏览器中会看到无论是请求作用域、还是会话作用域中都可以获取到登录者的信息。

图片描述

4. 小结

本节课程和大家一起讲解了 Model 、ModelMap、ModelAndView 数据模型,并讲解了可以使用 @SessionAttributes 注解提升数据模型的作用域。

从功能层面上讲,所有数据模型组件的功能是一样的。从底层上讲,区别在于实现 Map 的数据结构。

  • 直接使用 Map 做数据模型时, Spring MVC 注入的是一个 LinkedHashMap 实例。
  • ModelMap 是 LinkedHashMap 的子类,本质还是 LinkedHashMap,只是封装了 put 方法,让其显得高级一点。
  • Model 是接口,Spring MVC 注入的也是 LinkedHashMap 实例。

所以,无论是使用 Map 、Model 还是 ModelMap 其实都是在借助 LinkedHashMap 完成数据的存储。其区别在于应用场景,有时,需要接口注入,有时可能需要类注入。当然,还会有自己的功能实现。