在Spring MVC中,Model中的数据会被自动放入到请求域(Request Scope)中。也就是说,当我们在控制器中使用model.addAttribute()时,这些属性会被放入到HttpServletRequest对象的属性中。
让我们通过代码来详细解释:
- 控制器中设置属性:
@GetMapping("/list") public String list(Model model) { // 这里的数据会被放入请求域中 model.addAttribute("contentPage", "event/list"); return "layout/base"; }
- JSP中获取属性:
<!-- 以下三种写法是等价的 -->
<!-- 1. 直接使用EL表达式 -->
${contentPage}
<!-- 2. 显式指定从请求域获取 -->
${requestScope.contentPage}
<!-- 3. 使用JSP内置对象request -->
<%=request.getAttribute("contentPage")%>
在JSP中,有四个主要的域对象:
- 页面域(Page Scope):
- 最小的作用范围
- 仅在当前页面有效
- 通过pageContext对象访问
- 请求域(Request Scope):
- 在一次请求中有效
- Spring MVC的Model数据就是存在这里
- 通过request对象访问
- 会话域(Session Scope):
- 在一次会话中有效
- 用户登录信息通常存在这里
- 通过session对象访问
- 例如:${sessionScope.loginUser}
- 应用域(Application Scope):
- 在整个Web应用中有效
- 所有用户共享
- 通过application对象访问
在我们的项目中:
<!-- base.jsp -->
<!-- contentPage 从请求域获取 -->
<jsp:include page="/WEB-INF/views/${contentPage}.jsp"/>
<!-- 用户信息从会话域获取 -->
<c:if test="${not empty sessionScope.loginUser}">
<span>欢迎,${sessionScope.loginUser.username}</span>
</c:if>
当使用EL表达式${contentPage}时,它会按照以下顺序查找属性:
- 先找页面域(pageScope)
- 再找请求域(requestScope)
3. 再找会话域(sessionScope)
- 最后找应用域(applicationScope)
所以虽然我们在JSP中直接写${contentPage},但实际上它是从请求域中获取的,因为Spring MVC把Model中的数据都放在了请求域中。
这就是为什么:
- 页面切换时contentPage的值会改变(因为是请求域,每次请求都是新的)
- 而用户登录信息不会丢失(因为是会话域,会一直保持到用户退出登录)
这种域的概念是JSP中非常重要的一个概念,它们的作用范围不同,我们需要根据数据的特性选择合适的域来存储。
jsp页面有几种域
JSP页面有四种域(作用域/Scope),按照作用范围从小到大排序:
- 页面域 (Page Scope)
- 作用范围:仅在当前页面有效// 存值 <%pageContext.setAttribute("name", "value");%> // 取值 ${pageScope.name}
- 生命周期:当前页面的处理完成之前
- 适用场景:只在当前页面使用的数据
- 访问对象:pageContext
- 请求域 (Request Scope)
- 作用范围:一次请求内有效// 存值 <%request.setAttribute("name", "value");%> // 或在控制器中 model.addAttribute("name", "value"); // 取值 ${requestScope.name} // 或简写为 ${name}
- 生命周期:从请求开始到响应结束
- 适用场景:在一次请求中传递的数据(如从控制器传到JSP的数据)
- 访问对象:request
- 会话域 (Session Scope)
// 存值 <%session.setAttribute("loginUser", user);%> // 或在控制器中 session.setAttribute("loginUser", user); // 取值 ${sessionScope.loginUser}
- 作用范围:一次会话内有效
- 生命周期:从会话开始到会话结束(用户关闭浏览器或超时)
- 适用场景:用户相关的数据(如登录信息)
- 访问对象:session
- 应用域 (Application Scope)
- 作用范围:整个Web应用有效// 存值 <%application.setAttribute("appConfig", config);%> // 取值 ${applicationScope.appConfig}
- 生命周期:从服务器启动到服务器关闭
- 适用场景:所有用户共享的数据(如系统配置)
- 访问对象:application
在我们的项目中的实际应用:
1. 请求域:用于页面显示数据
// 控制器中
@GetMapping("/list")
public String list(Model model) {
model.addAttribute("contentPage", "event/list"); // 存入请求域
return "layout/base";
}
<!-- JSP中 -->
<jsp:include page="/WEB-INF/views/${contentPage}.jsp"/> // 从请求域获取
- 会话域:用于用户登录状态
// 控制器中 @PostMapping("/login") public Result<User> login(String username, String password, HttpSession session) { // ... 登录验证 ... session.setAttribute("loginUser", user); // 存入会话域 }
jsp<!-- JSP中 --><c:if test="${not empty sessionScope.loginUser}"> // 从会话域获取 <span>欢迎,${sessionScope.loginUser.username}</span></c:if>
数据查找顺序:
- 当使用${name}这样的EL表达式时,会按照以下顺序查找:
- pageScope
- requestScope
3. sessionScope
- applicationScope
选择使用哪个域的原则:
- 能用小范围的就不用大范围的
- 根据数据的生命周期选择合适的域
- 考虑性能影响(作用域越大,占用资源越多)