乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 SpringMVC概述与DispatcherServlet

    • 本章导读:Spring MVC概述与DispatcherServlet
    • Spring MVC 是什么
    • MVC 设计模式
    • 前端控制器模式
    • DispatcherServlet
    • 核心组件协作
  • 第2章 控制器与请求映射

    • 本章导读:控制器与请求映射
    • Controller
    • RestController
    • RequestMapping
    • GetMapping
    • PostMapping
    • PutMapping
    • DeleteMapping
    • PathVariable
    • RESTful
    • 请求映射原理
  • 第3章 请求参数获取与转换

    • 本章导读:请求参数获取与转换
    • RequestParam
    • RequestBody
    • RequestHeader
    • CookieValue
    • Model
    • ModelAttribute
    • 数据绑定原理
    • 数据校验
  • 第4章 响应数据与视图解析

    • 本章导读:响应数据与视图解析
    • ResponseBody
    • ResponseEntity
    • ModelAndView
    • ViewResolver
    • HttpMessageConverter
    • forward与redirect
  • 第5章 拦截器过滤器与跨域

    • 本章导读:拦截器、过滤器与跨域
    • HandlerInterceptor
    • WebMvcConfigurer
    • CrossOrigin
    • 登录验证实战
  • 第6章 文件上传与异常处理

    • 本章导读:文件上传与异常处理
    • MultipartFile
    • 文件下载
    • ExceptionHandler
    • ControllerAdvice
    • RestControllerAdvice
    • ResponseStatus
  • 第7章 高级特性与最佳实践

    • 本章导读:高级特性与最佳实践
    • SessionAttributes
    • SessionAttribute
    • RedirectAttributes
    • MockMvc测试
    • 国际化
    • 最佳实践
  • 第8章 扩展与异步机制

    • 本章导读:扩展与异步机制
    • 异步请求处理
    • 自定义参数解析器
    • 内容协商

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 数据流向

关键机制:

  1. BindingAwareModelMap:Spring MVC 实际注入的是 BindingAwareModelMap 实例,它同时实现了 Model、ModelMap、Map 接口
  2. 属性存储:addAttribute(String name, Object value) 将数据以键值对形式存入 Model
  3. 视图共享:Model 中的数据在视图渲染期间可读,渲染完成后生命周期结束
  4. 隐式 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 对比

特性ModelModelMapModelAndView
类型接口类(继承 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 怎么选?

标准回答:

  1. Model:最常用。方法返回 String 视图名,数据通过 Model 参数传递。代码简洁,职责分离清晰
  2. ModelMap:需要 Map 操作(如 putAll、containsKey)时使用。它继承自 LinkedHashMap,提供了 Map 的完整 API
  3. ModelAndView:视图名需要动态决定时使用(如根据权限返回不同页面)。它将视图名和数据封装在一个对象中返回

实际项目建议:优先使用 Model,除非有特殊需求才考虑另外两种。


小结

Model 是 Spring MVC 中 Controller 向视图传递数据的标准接口。它通过键值对形式存储数据,在视图渲染期间供模板引擎读取,最终生成 HTML 响应。

核心要点:

  • Model 只能作为方法参数由 Spring 注入,不能手动创建
  • 支持链式调用 addAttribute().addAttribute()
  • @RestController 不走视图解析,Model 在其中无效
  • Model、ModelMap、ModelAndView 三选一即可,优先用 Model

本章与全局的关系:本章完成了"请求参数获取与转换"章节的讲解。从 @RequestParam 的零散参数,到 @RequestBody 的结构化数据,再到 Model 的视图数据传递,形成了完整的请求数据流入链路。下一章将进入"响应数据与视图解析",讲解 @ResponseBody、视图解析器等输出机制。

上一页
CookieValue
下一页
ModelAttribute