Spring MVC 框架

一、基本定义

Spring MVC(全称 Spring Web MVC)是 Spring 框架原生提供的Web 层应用开发框架,基于经典的 MVC 设计模式实现,是 Java 企业级开发中最主流的 Web 开发方案之一,主要用于构建传统 Web 页面应用与 RESTful 接口服务。

它建立在 Servlet API 之上,对原生 Servlet 开发的繁琐流程做了深度封装,让开发者只需关注业务逻辑编写,无需手动处理参数解析、响应封装、路径映射等重复工作。


二、MVC 设计模式与 Spring MVC 的对应

MVC 是一种软件架构设计思想,将 Web 应用按职责拆分为三层,实现关注点分离:

  • Model(模型层):负责数据封装与核心业务逻辑,对应 Java 实体类、Service 业务层、DAO 数据层
  • View(视图层):负责数据展示与页面渲染,对应 JSP、Thymeleaf 模板页、前端页面
  • Controller(控制层):负责接收请求、调用模型、返回视图 / 数据,对应 Spring MVC 中的 Controller 组件

在当前前后端分离的主流场景下,View 层由前端独立实现,Spring MVC 通常只负责提供数据接口(返回 JSON),此时 Controller 直接返回业务数据对象,由框架自动序列化为 JSON 响应。


三、核心工作流程(请求全链路)

Spring MVC 的核心中枢是 DispatcherServlet(前端控制器),所有 HTTP 请求都会先经过它,再由它统一调度各个组件完成处理。完整请求处理流程如下:

  1. 用户发起 HTTP 请求,请求被 DispatcherServlet 统一接收
  2. DispatcherServlet 调用 HandlerMapping,根据请求 URL 匹配对应的 Controller 方法,并组装拦截器链
  3. DispatcherServlet 调用 HandlerAdapter,由适配器适配并执行目标 Controller 方法
  4. Controller 执行业务逻辑,返回处理结果(视图名、ModelAndView、JSON 对象等)
  5. HandlerAdapter 将执行结果返回给 DispatcherServlet
  6. 若返回视图名,DispatcherServlet 调用 ViewResolver(视图解析器),将逻辑视图名解析为具体的 View 对象
  7. DispatcherServlet 调用 View 对象进行视图渲染,将 Model 数据填充到视图中
  8. 最终 DispatcherServlet 将渲染结果或 JSON 数据作为 HTTP 响应返回给用户

四、核心组件说明

表格

组件 核心作用
DispatcherServlet 前端控制器,整个框架的调度中枢,统一接收请求并分发,降低各组件耦合
HandlerMapping 处理器映射器,建立请求 URL 与 Controller 方法的映射关系,同时组装拦截器链
HandlerAdapter 处理器适配器,采用适配器模式统一调用不同类型的 Controller,屏蔽调用方式的差异
Handler / Controller 业务处理器,由开发者编写,负责接收参数、调用业务层、返回处理结果
ViewResolver 视图解析器,将逻辑视图名(如 "login")解析为具体的视图对象(如 JSP、Thymeleaf 页面)
View 视图,负责数据渲染,将 Model 数据填充后生成最终的 HTML、JSON、Excel 等响应内容
HandlerInterceptor 拦截器,在请求处理的三个节点(Controller 执行前、执行后、请求结束后)插入自定义逻辑

五、核心优势与特性

  1. Spring 生态无缝集成 天然支持 Spring 的依赖注入(DI)、AOP、事务管理等能力,Spring 容器中的 Bean 可直接在 Controller 中注入使用。

  2. 注解驱动开发 通过 @Controller@RequestMapping@RequestParam@ResponseBody 等注解,极大简化配置,替代了传统 Servlet 繁琐的 web.xml 配置。

  3. 强大的参数绑定与转换 自动将请求参数(查询参数、表单、JSON 请求体)封装为 Java 对象,支持类型自动转换、数据校验、日期格式化等能力。

  4. 高度可扩展 支持自定义拦截器、参数解析器、消息转换器、视图解析器、异常处理器,可灵活适配各类业务需求。

  5. RESTful 友好 原生支持 REST 风格接口开发,通过 @RestController、路径变量、HTTP 方法语义化(GET/POST/PUT/DELETE)快速构建标准化接口。

  6. 多视图兼容 支持 JSP、Thymeleaf、Freemarker、Excel、PDF 等多种视图输出形式,满足不同业务场景。


六、补充:与 Servlet 过滤器的执行顺序

上一问提到的拦截器是 Spring MVC 的内部组件,而过滤器(Filter)是 Servlet 官方规范的组件,二者执行顺序为: Servlet 过滤器 → DispatcherServlet → Spring MVC 拦截器 → Controller

过滤器作用于请求进入 Servlet 容器的阶段,拦截器作用于 Spring MVC 内部的请求生命周期。

在 Spring Boot 环境中,Spring MVC 相关组件会被自动配置,开发者只需引入 spring-boot-starter-web 依赖即可直接使用,无需手动配置 DispatcherServlet 和基础视图解析器。

Spring MVC 常用注解详解(附完整示例)

以下按使用频率从高到低分类讲解,全部基于 Spring Boot 环境,覆盖日常开发 95% 以上的场景。


一、控制器类注解(类级别)

1. @Controller

  • 作用:标记一个类为 Spring MVC 控制器,负责接收并处理 HTTP 请求,传统页面开发中用于返回逻辑视图名。
  • 位置:类上方
  • 特点 :默认方法返回值会被当做视图名,走视图解析器渲染页面;若要返回 JSON 数据,需要在方法上额外加 @ResponseBody
java 复制代码
@Controller
@RequestMapping("/page")
public class PageController {
    // 返回 login 视图名,视图解析器会匹配到 login.html / login.jsp
    @GetMapping("/login")
    public String loginPage() {
        return "login";
    }
}

2. @RestController

  • 作用 :复合注解 = @Controller + @ResponseBody,是前后端分离项目的首选。
  • 位置:类上方
  • 特点 :类中所有方法默认直接返回数据(JSON/XML),不会走视图解析器,无需每个方法单独加 @ResponseBody
java 复制代码
@RestController
@RequestMapping("/api/user")
public class UserController {
    // 直接返回 JSON 格式的字符串/对象
    @GetMapping("/hello")
    public String hello() {
        return "hello spring mvc";
    }
}

二、请求映射注解(类 + 方法级别)

用于建立「请求 URL + 请求方式」与「处理方法」的映射关系。

1. @RequestMapping

  • 作用 :通用请求映射注解,可标注在方法上;类上定义父路径,方法上定义子路径。
  • 核心属性
    • value / path:请求路径,支持数组配置多个路径
    • method:指定请求方式(RequestMethod.GET / POST / PUT / DELETE 等)
    • params:限定请求必须包含 / 不包含指定参数
    • consumes:限定请求体的媒体类型(如 application/json
    • produces:限定响应的媒体类型
java 复制代码
@RestController
@RequestMapping("/api/user") // 类级别的父路径
public class UserController {
    // 匹配 GET /api/user/list?type=admin
    @RequestMapping(value = "/list", method = RequestMethod.GET, params = "type=admin")
    public String list() {
        return "管理员用户列表";
    }
}

2. REST 风格派生注解

等价于指定了 method@RequestMapping,是目前接口开发的主流写法:

注解 对应请求方式 典型用途
@GetMapping GET 查询数据
@PostMapping POST 新增数据
@PutMapping PUT 修改全量数据
@DeleteMapping DELETE 删除数据
@PatchMapping PATCH 修改部分数据
java 复制代码
@RestController
@RequestMapping("/api/user")
public class UserController {
    @GetMapping("/{id}")     // GET 查询单个用户
    public String getById(@PathVariable Long id) { return "查询用户:" + id; }

    @PostMapping            // POST 新增用户
    public String addUser() { return "新增用户"; }

    @PutMapping             // PUT 修改用户
    public String updateUser() { return "修改用户"; }

    @DeleteMapping("/{id}") // DELETE 删除用户
    public String deleteUser(@PathVariable Long id) { return "删除用户:" + id; }
}

三、请求参数绑定注解(方法参数级别)

这是开发中最核心的一类注解,用于把 HTTP 请求中的数据自动注入到方法参数中。

1. @RequestParam

  • 作用 :获取 URL 查询参数表单参数application/x-www-form-urlencoded
  • 核心属性
    • name / value:对应请求中的参数名
    • required:是否必填,默认 true
    • defaultValue:参数未传时的默认值
java 复制代码
@GetMapping("/page")
// 请求示例:GET /api/user/page?pageNum=1&pageSize=20
public String pageList(
        @RequestParam("pageNum") Integer pageNum,
        @RequestParam(defaultValue = "10") Integer pageSize // 不传默认10
) {
    return "第" + pageNum + "页,每页" + pageSize + "条";
}

2. @PathVariable

  • 作用 :获取 URL 路径中的变量,是 RESTful 风格接口的核心注解。
  • 场景 :把参数放在路径中,如 /user/100 中的 100
java 复制代码
@GetMapping("/{id}/{name}")
// 请求示例:GET /api/user/100/zhangsan
public String getInfo(
        @PathVariable Long id,
        @PathVariable String name
) {
    return "id:" + id + ", name:" + name;
}

3. @RequestBody

  • 作用 :接收 JSON 格式的请求体,自动将 JSON 反序列化为 Java 对象。
  • 场景:POST/PUT 请求提交复杂对象数据,前后端分离最常用。
  • 注意 :一个方法中最多只能有一个 @RequestBody;GET 请求没有请求体,不能使用。

先定义实体类:

java 复制代码
public class User {
    private Long id;
    private String username;
    private Integer age;
    // getter、setter、无参构造省略
}

使用示例:

java 复制代码
@PostMapping("/add")
// 请求体:{"id":1,"username":"张三","age":20}
public String addUser(@RequestBody User user) {
    return "新增用户:" + user.getUsername();
}

4. @RequestHeader

  • 作用:获取指定请求头的值
java 复制代码
@GetMapping("/header")
public String getHeader(@RequestHeader("token") String token) {
    return "请求头中的token:" + token;
}

5. @CookieValue

  • 作用:获取 Cookie 中的指定值
java 复制代码
@GetMapping("/cookie")
public String getCookie(@CookieValue("JSESSIONID") String sessionId) {
    return "SessionID:" + sessionId;
}

四、响应与状态注解

1. @ResponseBody

  • 作用:将方法返回值直接作为 HTTP 响应体输出,自动通过消息转换器序列化为 JSON。
  • 位置:方法上 / 类上
  • 说明@RestController 已经默认包含此注解,仅在 @Controller 类中需要返回 JSON 时单独使用。
java 复制代码
@Controller
public class TestController {
    @ResponseBody
    @GetMapping("/json")
    public User jsonTest() {
        return new User(1L, "张三", 20); // 直接返回 JSON 对象
    }
}

2. @ResponseStatus

  • 作用:自定义响应的 HTTP 状态码和提示信息
  • 位置:方法上 / 异常类上
java 复制代码
@PostMapping("/create")
@ResponseStatus(code = HttpStatus.CREATED, reason = "创建成功")
public void create() {
    // 业务逻辑,响应状态码为 201
}

五、全局增强与异常处理注解

1. @ControllerAdvice + @RestControllerAdvice

  • 作用 :控制器全局增强组件,用于实现全局异常处理全局数据绑定全局参数预处理
  • @RestControllerAdvice = @ControllerAdvice + @ResponseBody,统一返回 JSON 格式的异常结果。

2. @ExceptionHandler

  • 作用:捕获指定类型的异常,进行统一处理
  • 可写在 Controller 内做局部异常处理,也可配合 @RestControllerAdvice 做全局异常处理。

全局异常处理完整示例:

java 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {

    // 捕获参数非法异常
    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String handleIllegalArg(IllegalArgumentException e) {
        return "参数错误:" + e.getMessage();
    }

    // 捕获所有未处理的系统异常
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public String handleException(Exception e) {
        return "系统异常,请联系管理员";
    }
}

六、综合实战完整示例

1. 实体类 User.java

java 复制代码
public class User {
    private Long id;
    private String username;
    private Integer age;

    public User() {}
    public User(Long id, String username, Integer age) {
        this.id = id;
        this.username = username;
        this.age = age;
    }
    // getter、setter 省略
}

2. 用户控制器 UserController.java

java 复制代码
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
public class UserController {

    // 1. 路径变量 + GET 查询单个用户
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return new User(id, "用户" + id, 20);
    }

    // 2. 查询参数 + 分页列表
    @GetMapping("/list")
    public String listUsers(
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize
    ) {
        return "分页查询:第" + pageNum + "页,每页" + pageSize + "条";
    }

    // 3. JSON请求体 + 新增用户
    @PostMapping
    public String addUser(@RequestBody User user) {
        return "新增成功:" + user.getUsername();
    }

    // 4. 路径变量 + JSON请求体 + 修改用户
    @PutMapping("/{id}")
    public String updateUser(@PathVariable Long id, @RequestBody User user) {
        return "修改ID为" + id + "的用户:" + user.getUsername();
    }

    // 5. 路径变量 + 删除用户
    @DeleteMapping("/{id}")
    public String deleteUser(@PathVariable Long id) {
        return "删除用户ID:" + id;
    }
}

3. 全局异常处理 GlobalExceptionHandler.java

java 复制代码
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String paramError(IllegalArgumentException e) {
        return "请求参数非法:" + e.getMessage();
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public String systemError() {
        return "服务器内部错误,请稍后重试";
    }
}

七、常见易错点

  1. GET 请求不能用 @RequestBody :GET 协议没有请求体,传参只能用 @RequestParam@PathVariable
  2. 一个方法只能有一个 @RequestBody:请求体只有一个,无法拆分注入多个对象。
  3. @RestController 不会走视图解析器 :如果需要返回页面,必须用 @Controller
  4. @PathVariable 可选参数 :若要让路径变量可选,需配置 required = false,同时路径要支持空匹配。
相关推荐
犀利豆10 小时前
AI in Harness(二)
java·人工智能·后端
沉静的小伙11 小时前
在微服务中使用领域事件
java·运维·微服务
程序员在囧途11 小时前
likeadmin-api API 算力超市怎么做供应商切换?统一鉴权、task_id 和 callback_url 才能稳交付
java·服务器·数据库·开放api·likeadmin-api·api算力超市
醇氧11 小时前
Spring 容器 Map 注入机制详解
java·后端·spring
ylscode12 小时前
Cloudflare Workers Cache 深度解析:边缘缓存如何重塑无服务器性能边界
java·spring·mybatis
栈溢出了12 小时前
Java Lambda 表达式笔记
java·开发语言·intellij-idea
会周易的程序员12 小时前
使用 pybind11 封装 C++ 日志库 microLog 为 Python 模块
java·c++·python·物联网·架构·日志·aiot
zhixingheyi_tian12 小时前
一份GC日志的解读
java·开发语言
ywl47081208712 小时前
ArrayList和LinkedList内部的实现原理和区别
java·java基础
沉静的小伙12 小时前
Spring事务
java·数据库·spring