文章目录
一、回顾域对象
🔑Java Web中的四种域对象及其作用域(范围)
-
pageContext ->当前页面,范围最小。pageContext一般用于jsp页面,作用域仅为当前jsp页面,而Spring MVC不使用jsp,都是用html页面,所以下面仅介绍后面三种
-
request ->一次请求
-
session ->一次会话
-
application ->整个web工程,范围最大
🔑域对象的使用规则:在能实现功能的前提下,选用范围更小的域对象
二、在request域共享数据
2.1 使用ServletAPI
💬概述:直接使用原生的servlet API向request域中共享数据,即使用req.setAttribute("数据名", "数据值")
🔑使用
① 在控制器方法形参上添加HttpServletRequest
类型的对象req
,此时req
对象就表示当前请求,然后直接根据req
对象调用setAttribute()
方法,实现在request域中存储数据(与获取请求参数的使用类似)
@RequestMapping("/testScope01")
public String showTestScope01(HttpServletRequest req) {
// 直接调用方法向request域中存储数据
req.setAttribute("testData", "hello,servlet API!");
return "testScope";
}
② 在测试域对象页面testScope.html中获取requet域中的数据,request域中的数据可以直接通过数据名获取(下面四种方式都是采用该页面进行测试)
<p th:text="${testData}"></p>
💡 html页面中不能使用EL表达式,所以需要使用thymeleaf语法(
th:text
)来获取域对象数据,注意${"数据名"}
是写在th:text
中,不是写在<p>
的标签体内
2.2 使用ModelAndView对象
💬概述:modelAndView
对象是Spring MVC用来处理模型数据(model)和视图数据(view)的重要对象,Spring MVC使用域对象共享数据的5种方法底层都是通过modelAndView
对象实现(后续分析源码可知)
🔑分析modelAndView对象
modelAndView
可分为两个部分——model和view,两个部分负责不同功能
① model:表示模型数据,主要用于向request域中共享数据,相当于一个Model类型的对象
② view:表示视图数据,主要用于设置视图,实现页面的跳转modelAndView
对象的两个主要方法
①addObject("数据名", 数据值)
:通过数据名(String)和数据值(Object)往request域中存储数据,与原生servlet API的req.setAttribute()
方法类似
②setViewName("视图名")
:设置视图名称,该方法的功能与原先控制器中返回一个视图名的功能一样(return "视图名"
),都是设置一个视图名称交给视图解析器(thymeleaf)解析,获取到完整页面路径后实现页面跳转
🔑使用:创建控制器方法,方法返回类型为ModelAndView
,无需添加形参,在方法内先创建一个ModelAndView
类型的对象mav
,然后通过mav
对象分别调用addObject()
和setViewName()
两个方法,实现向域中存储数据和设置视图名,最后将mav
对象返回
❓ 为什么要把控制器方法返回值类型为
ModelAndView
:Spring MVC(或者说前端控制器DispatcherServlet
)需要获取modelAndView
对象,才能知道我们在域中存储的数据以及要转发的页面,而modelAndView
对象是在方法内创建的,不是作为方法形参,所以就必须要把我们创建的modelAndView
对象返回,Spring MVC才能获取到
@RequestMapping("/testScope02")
public ModelAndView showTestScope02() {
ModelAndView mav = new ModelAndView();
// 往request域中存储数据
mav.addObject("testData", "hello,ModelAndView!");
// 设置视图名
mav.setViewName("testScope");
// 最后返回mav对象
return mav;
}
2.3 使用Model对象
🔑分析model对象
model
对象相当于ModelAndView
对象中的model部分,主要用于向request域中存储数据model
对象的方法——addAttribute("数据名", 数据值)
:通过数据名和数据值向request域中存储数据,与mav.addObject()
和req.setAttribute()
用法一样
🔑使用:创建控制器方法,方法返回值类型为String,并添加一个Model
类型的形参model
,然后在方法中直接使用model
对象调用addAttribute()
方法,往request域中存储数据,最后再返回视图名
@RequestMapping("/testScope03")
public String showTestScope03(Model model) {
// 往request域中存储数据
model.addAttribute("testData", "hello,Model!");
return "testScope";
}
2.4 使用ModelMap对象
💬概述:ModelMap
对象和Model
对象类似,modelMap
对象同样通过是调用addAttribute()
方法实现往request域中存储数据
🔑使用
@RequestMapping("/testScope04")
public String showTestScope04(ModelMap modelMap) {
// 往request域中存储数据
modelMap.addAttribute("testData", "hello,ModelMap!");
return "testScope";
}
2.5 使用Map集合
💬概述:Spring MVC中可以使用Map集合向request域中存储数据,Map集合中的键值对(key-value)分别对应request域中的数据名(key)和数据值(value)
🔑使用:创建控制器方法,方法中添加Map<String, Object>
集合类型的形参scopeMap
,然后在方法中直接使用map集合的put()
方法,将数据名和数据值建立键值对关系,从而实现request域中存储数据
@RequestMapping("/testScope05")
public String showTestScope05(Map<String, Object> scopeMap) {
// 直接在map集合中存储数据
scopeMap.put("testData", "hello,Map!");
return "testScope";
}
2.6 Model、ModelMap和Map的关系
-
在上述测试控制器方法中分别打印出
model
、modelMap
和scopeMap
三个对象,观察控制台打印结果。打印结果显示,三个对象的打印结果类似,都是{数据名=数据值}
的形式
💡 一般直接打印对象,结果会显示对象的内存地址,说明实例化这三个对象的类中的
toString()
方法被重写过,且重写后的打印形式类型,说明实例化三个对象的类有可能是同一个 -
通过反射的方式——
对象.getClass().getName()
获取实例化三个对象的类的全类名并打印到控制台,打印结果发现,实例化三个对象的类是同一个,都是BindingAwareModelMap
类
-
分析四个类各自的源码
① Map:JDK中的底层接口
② Model:Spring MVC底层的接口(与其他类或接口无继承关系)
③ ModelMap:继承
LinkHashMap<String, Object>
类,而LinkHashMap<String, Object>
实现Map
接口
④ BindingAwareModelMap:继承
ExtendedModelMap
类,而ExtendedModelMap
类又继承ModelMap
,并实现Model
接口
-
四个类的关系图(类图):由类图中可以看出
BindingAwareModelMap
类与Model
、ModelMap
、Map
三个类或接口之间的关系,以及Model
、ModelMap
、Map
都能被BindingAwareModelMap
实例化
2.7 使用域对象的底层原理
🔑底层原理:在控制器方法执行后,前端控制器DispatcherServlet
会将控制器方法中的模型数据和视图数据封装成一个ModelAndView
对象,然后根据该对象向request域中共享数据,并将视图数据(视图名)交给视图解析器解析,最后实现页面跳转
🔑分析源码
-
在控制器方法中打上断点,debug模式启动,在页面点击链接跳转到断点处,此时在方法栈中可以看到所有间接或直接调用控制器方法的底层方法,我们关心前端控制器
DispatcherServlet
中的方法——doDispatch()
-
在
doDispatch()
方法中,可以看到ha.handle()
方法返回了一个mv
对象,该对象就是一个ModelAndView
对象,而ha.handle()
方法的执行还会间接或直接调用控制器方法,由此可得,前端控制器DispatcherServlet
在调用控制器方法之后,将控制器方法中的模型数据和视图数据封装成一个ModelAndView
对象并获取,然后再根据该对象来进行后续操作
-
在前端控制器获取
mv
对象中打上断点,进一步查看该对象的详细信息。mv
对象信息中,正好有控制器方法中设置的模型数据(往request域中存储数据)和视图数据(视图名),这里的控制器方法为showTestScope03()
,即使用model对应的控制器方法
三、在session域共享数据
💬概述:Spring MVC中可以通过原生的Servlet API获取session对象,然后再根据session对象调用setAttribute("数据名", 数据值)
方法往session域中存储数据
🔑使用
① 创建控制器方法,方法上添加HttpSession
类型的形参session
,然后根据session
对象调用方法,往session域中存储数据(与request域中共享数据类似)
@RequestMapping("/testSession")
public String showTestSession(HttpSession session) {
// 直接使用session对象调用方法,往session域中存储数据
session.setAttribute("testSessionData", "hello,session!");
return "testScope";
}
② 在testScope.html页面中通过thymeleaf语法获取session域中的数据,注意要使用thymeleaf内置对象(session)来获取域中的数据,不能直接写数据名(request域中数据获取才可以直接写数据名)
<!-- 获取session域中数据 -->
<p th:text="${session.testSessionData}"></p>
四、在application域共享数据
💬概述:Spring MVC中同样可以通过原生的Servlet API获取servletContext对象,然后再根据servletContext对象调用setAttribute("数据名", 数据值)
方法往application域中存储数据
🔧使用
① 创建控制器方法,方法上添加HttpSession
类型的形参session
,先根据session
对象获取servletContext
对象(或者说application
对象),然后根据servletContext
对象调用方法,往application域中存储数据
@RequestMapping("/testApplication")
public String showTestApplication(HttpSession session) {
// 直接使用session对象获取servletContext对象
ServletContext servletContext = session.getServletContext();
// 根据servletContext对象往application域中存储数据
servletContext.setAttribute("testApplicationData", "hello,application!");
return "testScope";
}
② 在testScope.html页面中通过thymeleaf语法获取application域中的数据,注意要使用thymeleaf内置对象(application)来获取域中的数据,不能直接写数据名
<!-- 获取application域中数据 -->
<p th:text="${application.testApplicationData}"></p>