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

    • 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章 扩展与异步机制

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

ModelAttribute

本章与 @RequestBody 形成互补。如果说 @RequestBody 是 RESTful API 时代处理 JSON 请求体的利器,那么 @ModelAttribute 就是传统表单提交时代的核心绑定机制。它负责将表单字段(application/x-www-form-urlencoded 或 multipart/form-data)绑定到 Java 对象,同时也承担着"请求预处理"的职责——在每次请求 Controller 方法前,向 Model 中注入公共数据。


定义与作用

@ModelAttribute 有两个截然不同的使用位置,对应两种完全不同的功能:

位置一:方法参数上——表单数据绑定

将 HTTP 请求中的表单字段绑定到 Java 对象。例如表单提交:

POST /employees
Content-Type: application/x-www-form-urlencoded

name=张三&department=技术部&salary=25000

@ModelAttribute 将这些分散的键值对组装成一个完整的 Employee 对象。

位置二:方法上——请求预处理

在 Controller 的每个请求处理方法执行前,先执行标注了 @ModelAttribute 的方法,将其返回值放入 Model 中。常用于准备下拉选项、部门列表等公共数据。

生活类比:入职登记表

想象飞翔科技的新员工入职流程:

  • @RequestBody(JSON):员工直接发了一封格式规范的电子邮件(JSON),HR 打印出来即可
  • @ModelAttribute(表单):员工填写了一张纸质入职登记表(HTML 表单),每个字段分散在表单的各个格子中。HR(@ModelAttribute)需要把这些格子里的信息,按对应关系抄录到员工档案(Java 对象)中

而标注在方法上的 @ModelAttribute,就像是"每次办理入职前,HR 先把公司部门列表、职位列表打印出来放在桌上"——这些公共资料每个入职流程都需要,提前准备好可以提高效率。


核心原理

表单绑定过程

关键机制:

  1. WebDataBinder:数据绑定器,负责将请求参数按名称匹配到对象属性,并执行类型转换
  2. 无参构造器:@ModelAttribute 必须先实例化对象,因此 Java 类必须有无参构造器
  3. 字段名匹配:表单字段名与 Java 属性名(通过 Getter/Setter 推导)匹配,支持嵌套属性(address.city)

方法级 @ModelAttribute 的执行时机

重要特性:

  • 方法级 @ModelAttribute 在当前 Controller 的每个请求处理方法之前执行
  • 返回值自动放入 Model,属性名默认按返回类型首字母小写(如 List<Department> → departmentList)
  • 可用 @ModelAttribute("depts") 显式指定 Model 中的属性名

适用位置与常用属性

使用位置作用常用属性
方法参数表单数据绑定到对象name / value:指定 Model 中的属性名;binding:是否启用数据绑定
方法请求预处理,向 Model 注入公共数据name / value:指定 Model 中的属性名

完整示例

场景

飞翔科技员工管理系统有一个"新增员工"页面,前端黄俪使用传统 HTML 表单提交(非 AJAX JSON)。表单包含姓名、部门、薪资字段。后端小崔使用 @ModelAttribute 接收表单数据。同时,每个页面都需要显示部门下拉列表,由方法级 @ModelAttribute 提前准备。

代码实现

package com.feixiang.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;

@Controller
@RequestMapping("/employees")
public class EmployeeController {

    /**
     * 方法级 @ModelAttribute:每次请求前准备部门列表
     */
    @ModelAttribute("departments")
    public List<String> prepareDepartments() {
        return Arrays.asList("技术部", "产品部", "运维部", "市场部");
    }

    /**
     * 显示新增员工表单页面
     * departments 已由 @ModelAttribute 方法注入 Model
     */
    @GetMapping("/new")
    public String showForm(Model model) {
        model.addAttribute("employee", new Employee());
        return "employee-form"; // 视图名
    }

    /**
     * 接收表单提交:表单字段 → Employee 对象
     */
    @PostMapping
    public String createEmployee(@ModelAttribute Employee employee, Model model) {
        model.addAttribute("message", "员工 " + employee.getName() + " 创建成功");
        return "success";
    }

    /**
     * 显式指定 Model 属性名
     */
    @PostMapping("/advanced")
    public String createAdvanced(@ModelAttribute("emp") Employee employee) {
        return "success";
    }
}

// Employee.java
package com.feixiang.web;

public class Employee {
    private String name;
    private String department;
    private Integer salary;

    public Employee() {}

    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; }
}

HTTP 请求示例

示例 1:表单提交(application/x-www-form-urlencoded)

curl -X POST "http://localhost:8080/employees" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d "name=张三" \
     -d "department=技术部" \
     -d "salary=25000"

响应(视图渲染模式,返回 HTML 页面):

<!-- success.html -->
<!DOCTYPE html>
<html>
<head><title>操作成功</title></head>
<body>
    <h1>员工 张三 创建成功</h1>
</body>
</html>

示例 2:表单字段名与属性名不匹配

curl -X POST "http://localhost:8080/employees" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d "userName=李四" \
     -d "dept=产品部"

响应页面中 employee.name 为 null,因为表单字段 userName 与 Java 属性 name 不匹配。解决方式:统一命名,或使用 @BindParam(Spring 不提供,需自定义 PropertyEditor)。


易错场景与面试考点

误区一:@ModelAttribute 和 @RequestBody 混用场景

错误代码:

@PostMapping
public String create(@ModelAttribute @RequestBody Employee employee) {
    // 错误!两者互斥
}

纠正:@ModelAttribute 和 @RequestBody 不能同时标注在同一个参数上。它们的绑定机制完全不同:

  • @ModelAttribute:从表单字段/查询参数绑定,使用 WebDataBinder
  • @RequestBody:从请求体读取完整 JSON/XML,使用 HttpMessageConverter

选择指南:

场景使用注解Content-Type
传统 HTML 表单提交@ModelAttributeapplication/x-www-form-urlencoded
文件上传表单@ModelAttributemultipart/form-data
AJAX 提交 JSON@RequestBodyapplication/json
简单查询参数@RequestParam任意

误区二:方法级 @ModelAttribute 被所有方法触发

现象:只想让 @ModelAttribute 方法在特定几个请求前执行,但它被所有方法触发了。

纠正:方法级 @ModelAttribute 默认作用于当前 Controller 的所有请求处理方法。如果只想限定特定 URL,可以结合 @RequestMapping:

@ModelAttribute
@RequestMapping("/employees/*")  // 仅对 /employees/* 路径生效
public void prepareData(Model model) {
    // ...
}

但更常见的做法是将公共数据准备逻辑提取到单独的 @ControllerAdvice 类中(本教程不展开 AOP 内容,仅作提示)。

误区三:忘记无参构造器

现象:@ModelAttribute Employee employee 报错,提示无法实例化。

纠正:@ModelAttribute 绑定需要先创建对象实例,再填充字段。如果 Employee 只有带参构造器,没有无参构造器,Spring 无法实例化。

解决:确保表单绑定对象有无参构造器:

public class Employee {
    public Employee() {}  // 必须有无参构造器
    // ...
}

面试高频:@ModelAttribute 标注在方法上和参数上的区别

标准回答:

  1. 标注在方法上:在 Controller 的每个请求处理方法执行前运行,返回值自动放入 Model,用于准备公共数据(如下拉列表、常量配置)
  2. 标注在参数上:将请求参数(表单字段或查询参数)绑定到 Java 对象,用于接收客户端提交的表单数据
  3. 执行时机:方法级先执行,参数级在调用目标方法时执行
  4. 底层机制:方法级直接调用方法返回值放入 Model;参数级使用 WebDataBinder 逐字段匹配绑定

小结

@ModelAttribute 是 Spring MVC 中兼具"表单绑定"和"数据预处理"双重身份的注解。标注在参数上时,它将分散的表单字段组装为完整的 Java 对象;标注在方法上时,它在每个请求前向 Model 注入公共数据。

核心要点:

  • 表单绑定对象必须有无参构造器
  • @ModelAttribute 与 @RequestBody 互斥,前者处理表单,后者处理 JSON
  • 方法级 @ModelAttribute 在当前 Controller 的所有请求前执行
  • 支持嵌套属性绑定(如 address.city)

本章与全局的关系:本章讲解了"表单数据的绑定与预处理"。下一节 Model 接口将讲解如何在 Controller 中向视图传递数据,这是连接"请求处理"与"视图渲染"的桥梁。

上一页
Model
下一页
数据绑定原理