Model
本章是请求参数获取章节的收尾,也是连接"请求处理"与"视图渲染"的关键桥梁。Controller 方法处理完业务逻辑后,需要将结果数据传递给视图(HTML 模板)。
Model接口就是 Spring MVC 提供的标准数据传递容器。理解 Model 的数据流向,是掌握视图渲染机制的前提。
定义与作用
Model 是 Spring MVC 提供的数据模型接口,用于在 Controller 和视图之间传递数据。
当 Controller 方法返回视图名(如 "employee-list")时,Spring MVC 需要同时把查询到的数据(如员工列表)交给视图引擎渲染。Model 就是这个"数据包裹"——Controller 把数据放进去,视图从里面取出来。
生活类比:公文袋
想象飞翔科技的文件流转:
- Controller:业务部门完成了一份报告(处理完业务逻辑)
- Model:公文袋,里面装着报告正文、附件、补充说明(各种数据)
- ViewResolver:根据公文袋上的标签(视图名),决定把公文袋送到哪个打印室(Thymeleaf、JSP 等)
- View:打印室把公文袋里的内容排版、打印成正式文件(渲染 HTML)
Model 本身不决定"怎么展示",它只负责"携带什么数据"。
核心原理
Model 数据流向
关键机制:
- BindingAwareModelMap:Spring MVC 实际注入的是
BindingAwareModelMap实例,它同时实现了Model、ModelMap、Map接口 - 属性存储:
addAttribute(String name, Object value)将数据以键值对形式存入 Model - 视图共享:Model 中的数据在视图渲染期间可读,渲染完成后生命周期结束
- 隐式 Model:即使方法参数不声明
Model,Spring MVC 也会隐式创建,用于存储@ModelAttribute方法和表单绑定结果
适用位置与常用方法
Model 作为接口,只能以 Controller 方法参数 的形式注入(由 Spring MVC 自动提供实例)。
| 方法 | 返回值 | 说明 |
|---|---|---|
addAttribute(String name, Object value) | Model | 添加属性,支持链式调用 |
addAttribute(Object value) | Model | 添加属性,名称按类型自动生成(如 Employee → employee) |
addAllAttributes(Map) | Model | 批量添加 |
containsAttribute(String name) | boolean | 判断是否包含某属性 |
Model vs ModelMap vs ModelAndView 对比
| 特性 | Model | ModelMap | ModelAndView |
|---|---|---|---|
| 类型 | 接口 | 类(继承 LinkedHashMap) | 类 |
| 携带视图名 | ❌ 否 | ❌ 否 | ✅ 是 |
| 携带数据 | ✅ 是 | ✅ 是 | ✅ 是 |
| 链式调用 | ✅ addAttribute().addAttribute() | ✅ Map 的 put() 也支持 | ❌ 需分别设置 |
| 使用场景 | 方法返回 String 视图名 | 需要 Map 操作的场景 | 动态决定视图名和数据 |
| 典型用法 | return "view" | return "view" | return new ModelAndView("view", model) |
选择建议:
- 视图名固定 → 用
Model+String返回值(最简洁) - 需要 Map 操作 → 用
ModelMap - 视图名动态决定 → 用
ModelAndView
完整示例
场景
飞翔科技员工管理系统的列表页面需要显示员工数据和分页信息。后端小崔从数据库查询后,通过 Model 将数据传递给 Thymeleaf 模板渲染。
代码实现
package com.feixiang.web;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Arrays;
import java.util.List;
@Controller
@RequestMapping("/employees")
public class EmployeeController {
/**
* 员工列表:Model 传递数据到视图
*/
@GetMapping
public String list(Model model) {
List<Employee> employees = Arrays.asList(
new Employee("张三", "技术部", 25000),
new Employee("李四", "产品部", 22000),
new Employee("王五", "运维部", 20000)
);
// 链式调用添加多个属性
model.addAttribute("employees", employees)
.addAttribute("pageTitle", "员工列表")
.addAttribute("totalCount", employees.size());
return "employee-list"; // 视图名
}
/**
* 使用 ModelMap(需要 Map 操作时)
*/
@GetMapping("/map")
public String listWithMap(org.springframework.ui.ModelMap modelMap) {
modelMap.put("employees", Arrays.asList(new Employee("赵六", "市场部", 18000)));
return "employee-list";
}
/**
* 使用 ModelAndView(动态视图名)
*/
@GetMapping("/detail")
public org.springframework.web.servlet.ModelAndView detail() {
org.springframework.web.servlet.ModelAndView mav =
new org.springframework.web.servlet.ModelAndView();
mav.setViewName("employee-detail");
mav.addObject("employee", new Employee("张三", "技术部", 25000));
return mav;
}
}
// Employee.java
package com.feixiang.web;
public class Employee {
private String name;
private String department;
private Integer salary;
public Employee() {}
public Employee(String name, String department, Integer salary) {
this.name = name;
this.department = department;
this.salary = salary;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public Integer getSalary() { return salary; }
public void setSalary(Integer salary) { this.salary = salary; }
}
Thymeleaf 模板示例
<!-- templates/employee-list.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="${pageTitle}">员工列表</title>
</head>
<body>
<h1 th:text="${pageTitle}">员工列表</h1>
<p>总人数: <span th:text="${totalCount}">0</span></p>
<table>
<tr>
<th>姓名</th>
<th>部门</th>
<th>薪资</th>
</tr>
<tr th:each="emp : ${employees}">
<td th:text="${emp.name}">张三</td>
<td th:text="${emp.department}">技术部</td>
<td th:text="${emp.salary}">25000</td>
</tr>
</table>
</body>
</html>
HTTP 请求示例
示例 1:请求员工列表页面
curl "http://localhost:8080/employees"
响应(渲染后的 HTML):
<!DOCTYPE html>
<html>
<head>
<title>员工列表</title>
</head>
<body>
<h1>员工列表</h1>
<p>总人数: <span>3</span></p>
<table>
<tr><th>姓名</th><th>部门</th><th>薪资</th></tr>
<tr><td>张三</td><td>技术部</td><td>25000</td></tr>
<tr><td>李四</td><td>产品部</td><td>22000</td></tr>
<tr><td>王五</td><td>运维部</td><td>20000</td></tr>
</table>
</body>
</html>
示例 2:Model 属性在视图中访问
Model 中的每个属性在视图中通过 ${属性名} 表达式访问:
${pageTitle}→"员工列表"${employees}→List<Employee>${totalCount}→3
易错场景与面试考点
误区一:Model 在 @RestController 中无效
现象:@RestController 的方法中注入 Model,添加属性后返回视图名,但客户端收到的是纯文本字符串。
原因:@RestController 等价于 @Controller + @ResponseBody,所有返回值直接序列化为响应体,不走视图解析。
纠正:需要视图渲染时,使用 @Controller:
@Controller // 不是 @RestController
public class EmployeeController {
@GetMapping("/employees")
public String list(Model model) {
model.addAttribute("data", ...);
return "employee-list"; // 走视图解析
}
}
误区二:Model 属性名冲突
现象:@ModelAttribute 方法和方法内 model.addAttribute 添加了同名属性,后者覆盖了前者。
纠正:Spring MVC 的 Model 是 Map 结构,同名属性后添加的会覆盖先添加的。注意命名空间管理:
@ModelAttribute("departments") // 方法级准备
public List<String> prepareDepts() { ... }
@GetMapping("/list")
public String list(Model model) {
// 不要覆盖 departments,除非有意更新
model.addAttribute("employees", ...); // 使用不同名字
return "list";
}
误区三:试图手动 new Model
错误代码:
@GetMapping("/test")
public String test() {
Model model = new Model(); // 错误!Model 是接口,不能实例化
model.addAttribute("key", "value");
return "view";
}
纠正:Model 必须由 Spring MVC 注入。正确写法:
@GetMapping("/test")
public String test(Model model) { // Spring 自动注入 BindingAwareModelMap
model.addAttribute("key", "value");
return "view";
}
面试高频:Model、ModelMap、ModelAndView 怎么选?
标准回答:
- Model:最常用。方法返回 String 视图名,数据通过 Model 参数传递。代码简洁,职责分离清晰
- ModelMap:需要 Map 操作(如
putAll、containsKey)时使用。它继承自LinkedHashMap,提供了 Map 的完整 API - ModelAndView:视图名需要动态决定时使用(如根据权限返回不同页面)。它将视图名和数据封装在一个对象中返回
实际项目建议:优先使用 Model,除非有特殊需求才考虑另外两种。
小结
Model 是 Spring MVC 中 Controller 向视图传递数据的标准接口。它通过键值对形式存储数据,在视图渲染期间供模板引擎读取,最终生成 HTML 响应。
核心要点:
Model只能作为方法参数由 Spring 注入,不能手动创建- 支持链式调用
addAttribute().addAttribute() @RestController不走视图解析,Model在其中无效Model、ModelMap、ModelAndView三选一即可,优先用Model
本章与全局的关系:本章完成了"请求参数获取与转换"章节的讲解。从 @RequestParam 的零散参数,到 @RequestBody 的结构化数据,再到 Model 的视图数据传递,形成了完整的请求数据流入链路。下一章将进入"响应数据与视图解析",讲解 @ResponseBody、视图解析器等输出机制。