参考视频:SpringMVC教程,SpringMVC从零到精通,老杜SpringMVC,动力节点SpringMVC

1. Servlet中的三个域对象

  • 请求域:request

  • 会话域:session

  • 应用域:application

三个域都有以下三个方法:

1
2
3
4
5
6
7
8
// 向域中存储数据
void setAttribute(String name, Object obj);

// 从域中读取数据
Object getAttribute(String name);

// 删除域中的数据
void removeAttribute(String name);

主要是通过:setAttribute + getAttribute 方法来完成在域中数据的传递共享

1.1 request

接口名:HttpServletRequest

简称:request

request对象代表了一次请求。一次请求一个request。

使用请求域的业务场景:在A资源中通过转发的方式跳转到B资源,因为是转发,因此从A到B是一次请求,如果想让A资源和B资源共享同一个数据,可以将数据存储到request域中。

1.2 session

接口名:HttpSession

简称:session

session对象代表了一次会话。从打开浏览器开始访问,到最终浏览器关闭,这是一次完整的会话。每个会话session对象都对应一个JSESSIONID,而JSESSIONID生成后以cookie的方式存储在浏览器客户端。浏览器关闭,JSESSIONID失效,会话结束。

使用会话域的业务场景:

  • 在A资源中通过重定向的方式跳转到B资源,因为是重定向,因此从A到B是两次请求,如果想让A资源和B资源共享同一个数据,可以将数据存储到session域中。
  • 登录成功后保存用户的登录状态。

1.3 application

接口名:ServletContext

简称:application

application对象代表了整个web应用,服务器启动时创建,服务器关闭时销毁。对于一个web应用来说,application对象只有一个。

使用应用域的业务场景:记录网站的在线人数。

2. request域对象

在SpringMVC中,在request域中共享数据有以下几种方式:

  • 使用原生Servlet API方式
  • 使用Model接口
  • 使用Map接口
  • 使用ModelMap类
  • 使用ModelAndView类

2.1 使用原生Servlet API方式

1、新建一个模块(步骤参考前面模块),创建 IndexControllerindex.html,代码如下:

1
2
3
4
5
6
7
8
@Controller
public class IndexController {

@RequestMapping("/")
public String index(){
return "index";
}
}

2、创建index.html并添加超链接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<h1>测试三个域对象</h1>
<hr>
<h2>测试request域对象</h2>
<a th:href="@{/testServletAPI}">测试在SpringMVC当中使用原生Servlet API完成request域数据共享</a><br>

</body>
</html>

3、在Controller的方法上使用HttpServletRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
public class RequestScopeTestController {

@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request) {
// 向request域中存储数据
request.setAttribute("testRequestScope", "在SpringMVC中使用原生Servlet API实现request域数据共享");
// 跳转视图,在视图页面将request域中的数据取出来,这样就完成了:Controller和View在同一个请求当中两个组件之间数据的共享。
// 这个跳转,默认情况下是:转发的方式。(转发forward是一次请求)
// 这个返回的是一个逻辑视图名称,经过视图解析器解析,变成物理视图名称。/WEB-INF/thymeleaf/view.html
return "view";
}
}

4、创建view.html页面:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>view</title>
</head>
<body>
<div th:text="${testRequestScope}"></div>
</body>
</html>

5、启动服务器,测试结果:

测试使用原生API方式共享数据

6、点击链接后成功跳转,并且携带数据:

成功跳转且携带数据

这种方式当然可以,用SpringMVC框架,不建议使用原生Servlet API。

2.2 使用Model接口

1
2
3
4
5
6
7
8
9
@RequestMapping("/testModel")
public String testModel(Model model) {
// 向request域当中绑定数据
model.addAttribute("testRequestScope", "在SpringMVC当中使用Model接口完成request域数据共享");
System.out.println(model);
System.out.println(model.getClass().getName());
// 转发
return "view";
}

在index.html页面添加超链接:

1
<a th:href="@{/testModel}">测试在SpringMVC当中使用Model接口完成request域数据共享</a><br>

2.3 使用Map接口

1
2
3
4
5
6
7
8
9
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
// 向request域当中存储数据
map.put("testRequestScope", "在SpringMVC当中使用Map接口完成request域数据共享");
System.out.println(map);
System.out.println(map.getClass().getName());
// 转发
return "view";
}

在index.html页面添加超链接:

1
<a th:href="@{/testMap}">测试在SpringMVC当中使用Map接口完成request域数据共享</a><br>

2.4 使用ModelMap类

1
2
3
4
5
6
7
8
9
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
// 向request域当中存储数据
modelMap.addAttribute("testRequestScope", "在SpringMVC当中使用ModelMap类完成request域数据共享");
System.out.println(modelMap);
System.out.println(modelMap.getClass().getName());
// 转发
return "view";
}

在index.html页面添加超链接:

1
<a th:href="@{/testModelMap}">测试在SpringMVC当中使用ModelMap类完成request域数据共享</a><br>

Model、Map、ModelMap的关系

可以在以上Model、Map、ModelMap的测试程序中将其和其类名输出,看看输出什么:

1
2
3
4
5
6
7
8
{testRequestScope=在SpringMVC当中使用Model接口完成request域数据共享}
org.springframework.validation.support.BindingAwareModelMap

{testRequestScope=在SpringMVC当中使用Map接口完成request域数据共享}
org.springframework.validation.support.BindingAwareModelMap

{testRequestScope=在SpringMVC当中使用ModelMap类完成request域数据共享}
org.springframework.validation.support.BindingAwareModelMap

通过输出结果可以看出,无论是Model、Map还是ModelMap,底层实例化的对象都是:BindingAwareModelMap

可以查看BindingAwareModelMap的继承结构:

BindingAwareModelMap继承结构

通过继承结构可以看出:BindingAwareModelMap继承了ExtendedModelMap,而ExtendedModelMap又继承了ModelMap并且实现了Model接口。ModelMap顶层又有一个Map接口。因此表面上是采用了不同方式,底层本质上是相同的。

SpringMVC之所以提供了这些方式,目的就是方便程序员的使用,提供了多样化的方式,可见它的重要性。

2.5 使用ModelAndView类

在SpringMVC框架中为了更好的体现MVC架构模式,提供了一个类:ModelAndView。这个类的实例封装了Model和View。也就是说这个类既封装业务处理之后的数据,也体现了跳转到哪个视图。使用它也可以完成request域数据共享。

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
// 创建“模型与视图对象”
ModelAndView modelAndView = new ModelAndView();
// 绑定数据
modelAndView.addObject("testRequestScope", "在SpringMVC中使用ModelAndView实现request域数据共享");
// 绑定视图
modelAndView.setViewName("view");
// 返回
return modelAndView;
}

在index.html页面添加超链接:

1
<a th:href="@{/testModelAndView}">测试在SpringMVC当中使用ModelAndView类完成request域数据共享</a><br>

这种方式需要注意的是:

  • 方法的返回值类型不是String,而是ModelAndView对象
  • ModelAndView不是出现在方法的参数位置,而是在方法体中new的
  • 需要调用addObject向域中存储数据
  • 需要调用setViewName设置视图的名字

ModelAndView源码分析

以上我们通过了五种方式完成了request域数据共享,包括:原生Servlet APIModelMapModelMapModelAndView

其中后四种:Model、Map、ModelMap、ModelAndView。这四种方式在底层DispatcherServlet调用我们的Controller之后,返回的对象都是ModelAndView,这个可以通过源码进行分析。

在以上四种方式中,拿Model举例,添加断点进行调试:

添加断点进行调试

debug模式启动服务器,发送请求,走到断点:

走到断点

查看VM Stack信息:

查看VM Stack信息

查看DispatcherServlet,源码如下:

DispatcherServlet源码

可以看到这里,无论你使用哪种方式,最终都要返回一个ModelAndView对象

PS:大家可以通过以下断点调试方式,采用一级一级返回,最终可以看到都会返回ModelAndView对象。

Step Out按钮

3. session域对象

在SpringMVC中使用session域共享数据,实现方式有多种,其中比较常见的两种方式:

  • 使用原生Servlet API
  • 使用SessionAttributes注解

3.1 使用原生Servlet API

1
2
3
4
5
6
7
8
9
10
11
@Controller
public class SessionScopeTestController {
@RequestMapping("/testSessionServletAPI")
public String testServletAPI(HttpSession session) {
// 处理核心业务....
// 将数据存储到session中
session.setAttribute("testSessionScope", "在SpringMVC当中使用原生Servlet API完成session域数据共享");
// 返回逻辑视图名称(这是一个转发的行为)
return "view";
}
}

在view.html视图页面添加:

1
<div th:text="${session.testSessionScope}"></div>

index.html添加超链接:

1
2
<h2>测试session域对象</h2>
<a th:href="@{/testSessionServletAPI}">测试在SpringMVC当中使用原生Servlet API完成session域数据共享</a><br>

3.2 使用SessionAttributes注解

使用@SessionAttributes注解标注Controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Controller
//@SessionAttributes(value = {"x", "y"})
@SessionAttributes({"x", "y"}) // 标注x和y都是存放到session域中,而不是request域。
public class SessionScopeTestController {

@RequestMapping("/testSessionAttributes")
public String testSessionAttributes(ModelMap modelMap) {
// 处理业务
// 将数据存储到session域当中
modelMap.addAttribute("x", "我是埃克斯");
modelMap.addAttribute("y", "我是歪");

// 返回逻辑视图名称
return "view";
}
}

PS:SessionAttributes注解使用在Controller类上。标注了当key是 x 或者 y 时,数据将被存储到会话session中。如果没有 @SessionAttributes注解,默认存储到request域中。😅

在view.html视图页面添加:

1
2
<div th:text="${session.x}"></div>
<div th:text="${session.y}"></div>

在index.html页面添加超链接:

1
<a th:href="@{/testSessionAttributes}">测试在SpringMVC当中使用@SessionAttributes注解完成session域数据共享</a><br>

4. application域对象

在SpringMVC实现application域数据共享,最常见的方案就是直接使用Servlet API了:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
public class ApplicationScopeTestController {

@RequestMapping("/testApplicationScope")
public String testApplicationScope(HttpServletRequest request) {
// 将数据存储到application域当中
// 获取application对象,其实就是获取ServletContext对象
// 怎么获取ServletContext对象?通过request,通过session都可以获取。
ServletContext application = request.getServletContext();
application.setAttribute("testApplicationScope", "在SpringMVC中使用ServletAPI实现application域共享");
return "view";
}
}

在view.html视图页面添加:

1
<div th:text="${application.testApplicationScope}"></div>

在index.html页面添加超链接:

1
2
<h2>测试application域对象</h2>
<a th:href="@{/testApplicationScope}">测试在SpringMVC当中使用Servlet API实现application域数据共享</a><br>