Spring MVC 模型数据处理详解:4 种核心方式与错误处理
在 Spring MVC 开发中,将业务数据传递到视图(如 JSP、Thymeleaf)是核心需求之一。Spring MVC 提供了 ModelAndView、Map/Model、@SessionAttributes、@ModelAttribute 四种灵活的模型数据处理方式,覆盖从 “单次请求数据传递” 到 “跨请求数据共享” 的全场景。从 “数据传递原理→使用场景→实战示例” 三个维度,彻底讲透模型数据的处理逻辑。
核心概念:模型数据的本质
Spring MVC 中的 “模型数据” 本质是 键值对(Key-Value),存储在 Model 或 ModelMap 中,最终会通过 HttpServletRequest.setAttribute() 绑定到请求域(Request Scope),视图技术(如 JSP 的 EL 表达式 ${key})可直接访问这些数据。
- 作用域分类:
- 请求域(Request Scope):数据仅在当前请求有效(默认,如 ModelAndView、Map 传递的数据);
- 会话域(Session Scope):数据在当前用户会话中有效(需通过
@SessionAttributes显式设置)
方式 1:ModelAndView —— 视图与数据的统一载体
ModelAndView 是 Spring MVC 早期最常用的模型数据处理方式,同时封装 “视图信息” 和 “模型数据”,适合需要明确指定视图且传递数据的场景。
1. 核心原理与源码解析
ModelAndView 内部维护两个核心变量,分别对应 “视图” 和 “数据”:
1 | public class ModelAndView { |
- 底层绑定逻辑:Spring MVC 会将
ModelAndView中的模型数据,通过request.setAttribute(key, value)绑定到请求域,视图渲染时可直接访问。
2. 实战示例
1 | import org.springframework.web.bind.annotation.GetMapping; |
视图渲染(JSP 示例):
1
2
3
4
5
6
7
8<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<h1>${title}</h1> <!-- 输出:用户列表 -->
<p>当前时间:${currentTime}</p> <!-- 输出:当前日期时间 -->
<p>用户信息:${user.name}(${user.age}岁)</p> <!-- 输出:张三(25岁) -->
</body>
</html>
方式 2:Map/Model/ModelMap —— 方法入参传递数据
Spring MVC 支持将 Map、Model、ModelMap 作为 Controller 方法的入参,方法执行时会自动注入这些对象,添加的数据会被 Spring 自动封装到模型中,无需手动创建 ModelAndView。
1. 三种类型的关系与区别
| 类型 | 继承关系 | 核心特点 | 适用场景 |
|---|---|---|---|
Map |
JDK 原生接口(java.util.Map) |
最简洁,仅支持基本的键值对操作 | 简单数据传递,无需额外功能 |
Model |
Spring 接口(org.springframework.ui.Model) |
提供 addAttribute() 等方法,方法名更语义化 |
推荐使用,Spring 官方推荐 |
ModelMap |
继承 LinkedHashMap,实现 Model |
兼具 Map 的灵活性和 Model 的语义化方法,支持排序 | 需要有序存储或额外方法(如 clear()) |
- 底层实现:无论使用哪种类型,Spring MVC 实际注入的都是
BindingAwareModelMap(ModelMap的子类),三种类型的使用效果完全一致,仅 API 风格不同。
2. 实战示例
1 | import org.springframework.ui.Model; |
视图渲染(Thymeleaf 示例):
1
2
3
4
5
6
7
8
<html xmlns:th="http://www.thymeleaf.org">
<body>
<p th:text="${message}"></p> <!-- 输出对应方法传递的消息 -->
<p th:text="${count != null ? '数量:' + count : ''}"></p> <!-- 仅 testMap 方法有值 -->
<p th:text="${user != null ? '用户名:' + user.name : ''}"></p> <!-- 仅 testModel 方法有值 -->
</body>
</html>
方式 3:@SessionAttributes —— 跨请求数据共享
@SessionAttributes 是 类级别的注解,用于将 “请求域中的模型数据” 提升到 “会话域(HttpSession)”,实现多个请求之间的数据共享(如用户登录信息、跨页面表单数据)。
1. 核心原理
- 标注位置:仅能标注在
@Controller类上,作用于该类的所有方法; - 数据提升逻辑:当 Controller 方法向模型中添加
@SessionAttributes指定的键名数据时,Spring MVC 会自动将该数据同步到HttpSession; - 数据销毁:需手动调用
SessionStatus.setComplete()销毁会话域中的数据(否则数据会一直保存在 Session 中,直到 Session 过期)。
2. 实战示例
1 | import org.springframework.stereotype.Controller; |
- 注意事项:
- 避免滥用:
@SessionAttributes管理的 Session 数据需手动销毁,否则会占用内存,建议仅用于短期共享的数据; - 键名匹配:仅模型中键名与
@SessionAttributes一致的数据才会被同步到 Session。
- 避免滥用:
方式 4:@ModelAttribute —— 预置模型数据或绑定请求参数
@ModelAttribute 是 灵活的注解,可标注在 “方法” 或 “方法参数” 上,分别实现 “预置模型数据” 和 “绑定请求参数到模型” 的功能。
1. 标注在方法上:预置模型数据
- 作用:在当前 Controller 的所有方法执行前,先执行被
@ModelAttribute标注的方法,将其返回值或添加的数据存入模型,实现 “数据预置”(如全局查询字典数据、用户信息)。 - 执行时机:被
@ModelAttribute标注的方法,会在当前 Controller 的每个请求处理方法(如@GetMapping、@PostMapping方法)执行前调用。
实战示例:预置全局字典数据
1 | import org.springframework.ui.Model; |
视图渲染:
1
2
3
4
5
6<!-- 直接访问 预置的数据 -->
<select name="gender">
<option value="1">${genderDict['1']}</option> <!-- 输出:男 -->
<option value="2">${genderDict['2']}</option> <!-- 输出:女 -->
</select>
<p>当前登录:${loginUser.name}</p> <!-- 输出:管理员 -->
2. 标注在方法参数上:绑定请求参数到模型
- 作用:将请求参数自动绑定到方法参数对象,并将该对象存入模型(无需手动
model.addAttribute()),适合表单提交场景。 - 绑定逻辑:Spring MVC 会根据 “请求参数名” 与 “对象属性名” 的匹配关系,自动填充属性值(如请求参数
name=张三绑定到User对象的name属性)。
实战示例:表单提交与参数绑定
1 | import org.springframework.web.bind.annotation.ModelAttribute; |
表单页面(JSP):
1
2
3
4
5<form action="/submitUser" method="post">
姓名:<input type="text" name="name" /> <!-- 参数名与 User 属性名一致 -->
年龄:<input type="number" name="age" />
<button type="submit">提交</button>
</form>结果页面:
1
2
3<p>提交成功!</p>
<p>姓名:${user.name}</p> <!-- 输出提交的姓名 -->
<p>年龄:${user.age}</p> <!-- 输出提交的年龄 -->
模型数据绑定错误处理:BindingResult 与 Errors
当模型数据绑定或校验失败时(如参数类型不匹配、校验注解不满足),Spring MVC 会将错误信息封装到 BindingResult 或 Errors 对象中,开发者可通过这些对象处理错误。
1. 核心依赖:JSR-380 校验注解
需先添加校验依赖(如 Hibernate Validator),使用 @NotNull、@NotBlank 等注解标注模型属性:
1 | <!-- Maven 依赖 --> |
2. 实战示例:校验与错误处理
1 | import org.springframework.validation.BindingResult; |
视图显示错误(Thymeleaf 示例):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<form th:action="@{/submitForm}" method="post">
<!-- 姓名输入框 + 错误提示 -->
<div>
<label>姓名:</label>
<input type="text" th:field="${userForm.name}" />
<!-- 显示姓名的错误信息 -->
<span th:if="${#fields.hasErrors('name')}" th:errors="*{name}" style="color:red"></span>
</div>
<!-- 年龄输入框 + 错误提示 -->
<div>
<label>年龄:</label>
<input type="number" th:field="${userForm.age}" />
<!-- 显示年龄的错误信息 -->
<span th:if="${#fields.hasErrors('age')}" th:errors="*{age}" style="color:red"></span>
</div>
<button type="submit">提交</button>
</form>
四种模型数据方式对比与选择建议
| 方式 | 核心优势 | 适用场景 | 数据作用域 |
|---|---|---|---|
| ModelAndView | 统一封装视图和数据,逻辑清晰 | 需要明确指定视图且传递数据 | 请求域(默认) |
| Map/Model/ModelMap | 轻量灵活,无需创建额外对象 | 仅传递数据,视图通过返回值指定 | 请求域(默认) |
| @SessionAttributes | 跨请求数据共享 | 用户登录信息、跨页面表单数据 | 会话域 |
| @ModelAttribute | 预置全局数据或自动绑定请求参数 | 全局字典数据、表单参数绑定 | 请求域(默认) |
选择建议
- 简单数据传递:优先使用
Model(语义化好,Spring 推荐); - 明确指定视图:使用
ModelAndView(适合早期非注解式开发,现在较少用); - 跨请求共享:使用
@SessionAttributes(注意手动销毁数据); - 全局数据预置或表单绑定:使用
@ModelAttribute; - 数据校验:必须配合
@Validated和BindingResult。
总结
Spring MVC 模型数据处理的核心是 “将数据绑定到合适的作用域”,通过四种方式覆盖不同场景需求:
- 单次请求数据:
Model、ModelAndView; - 跨请求数据:
@SessionAttributes; - 全局预置或表单绑定:
@ModelAttribute; - 错误处理:
BindingResult