Skip to content

Spring MVC

Spring MVC 是 Spring 体系里最常见的 Web 框架之一。

很多人对它的印象停留在:写 Controller,收参数,返回 JSON。

这当然没错,但还是太表面。Spring MVC 真正解决的是:一个 HTTP 请求进入 Java Web 应用之后,怎么被接收、路由、绑定参数、调用业务方法,并最终返回响应。

这篇主要看:

  1. Spring MVC 到底是什么
  2. 它解决什么问题
  3. MVC 在这里具体是什么意思
  4. 一次请求在框架里是怎么流动的
  5. 工程上最容易混的点有哪些

1. Spring MVC 到底是什么

Spring MVC 直接看成:Spring 体系里的 Web MVC 框架。

它主要负责处理:

  1. HTTP 请求接入
  2. URL 路由分发
  3. 参数绑定
  4. 调用控制层方法
  5. 返回页面或 JSON 响应

所以它不是整个 Spring,也不是整个 Boot,而是 Spring 在 Web 层的核心实现。


2. MVC 到底是什么意思

MVC 是:

  1. Model

  2. View

  3. Controller

  4. Controller:接请求、调业务、组织返回

  5. Model:承载业务数据

  6. View:负责展示结果

在现代前后端分离项目里,View 往往不再是后端模板页面,而更多变成:直接返回 JSON,由前端自己渲染。

所以很多人虽然常写的是 @RestController,但底层仍然是 MVC 这条主线的延伸。


3. Spring MVC 主要解决什么问题

3.1 请求怎么找到正确方法

一个请求进来后,总得先知道:到底该由哪个 Controller、哪个方法来处理。

Spring MVC 负责做这件事。

3.2 参数怎么自动绑定

请求里可能带:

  1. 路径参数
  2. Query 参数
  3. 表单参数
  4. JSON 请求体
  5. Header

如果每次都手动解析,开发成本会很高。

Spring MVC 把这些绑定规则统一起来。

3.3 返回结果怎么统一处理

有的方法返回页面,有的方法返回 JSON,有的方法返回状态码和统一响应体。

Spring MVC 会把这些输出流程也统一起来。

3.4 异常怎么统一处理

如果业务方法里抛异常,不能每个 Controller 都自己手写一套兜底。

Spring MVC 提供统一异常处理机制。


4. 一次请求在 Spring MVC 里是怎么流动的

这是最核心的主线。

可以按这条链路理解:

  1. 请求先进入 DispatcherServlet
  2. 它去找谁能处理这个请求
  3. 找到对应的 Controller 方法
  4. 做参数绑定和必要转换
  5. 调用目标方法
  6. 拿到返回值
  7. 再把返回值转换成页面或 JSON 响应

这里最关键的角色是 DispatcherServlet

如果你想继续把“请求进入应用之后,到底经过谁、最后落到谁”这条线看清,可以接着看:

把它画成流程图,大致就是这样:

mermaid
flowchart TD
    A[HTTP 请求进入应用] --> B[DispatcherServlet]
    B --> C[HandlerMapping 查找处理器]
    C --> D[定位到 Controller 方法]
    D --> E[HandlerAdapter 负责调用]
    E --> F[参数解析与数据绑定]
    F --> G[调用 Controller 方法]
    G --> H{返回值类型}
    H -->|页面| I[ViewResolver 解析视图]
    H -->|JSON 或响应体| J[HttpMessageConverter 转换响应]
    I --> K[返回 HTTP 响应]
    J --> K

5. DispatcherServlet 是什么

DispatcherServlet 可以理解成 Spring MVC 的前端控制器

这里的“前端”不是浏览器前端,而是:所有 Web 请求进入 Spring MVC 时的统一入口。

它解决的是 请求不要直接散着进各个 Controller,而是先经过一个统一调度中心

这也是为什么它叫 Dispatcher,就是“分发器”。


6. HandlerMappingHandlerAdapter 这些词到底在说什么

这些名词很容易把人绕晕,但只要抓住角色就没那么可怕。

6.1 HandlerMapping

它解决的是 这个请求该映射到哪个处理器

可以简单理解成“找人”。

6.2 HandlerAdapter

它解决的是 找到人之后,怎么用统一方式把这个处理器真正调起来

可以简单理解成“负责调用”。

所以整条链路可以这么记:

  1. DispatcherServlet 负责统一接入
  2. HandlerMapping 负责找处理器
  3. HandlerAdapter 负责实际调用

7. ControllerRestController 有什么区别

7.1 @Controller

它更偏传统 MVC 里的控制层。

在返回页面模板场景里很常见。

7.2 @RestController

它可以理解成 @Controller + @ResponseBody

它更偏:方法返回值直接作为响应体输出,而不是去解析视图。

这也是为什么前后端分离项目里,@RestController 特别常见。

一句话压缩:

  1. @Controller 更偏页面控制器
  2. @RestController 更偏接口控制器

7.3 @RestController 为什么说是组合注解

这是一个很值得单独记住的点。

@RestController = @Controller + @ResponseBody

它不是凭空多出来的新能力,而是把:

  1. 这个类是控制器
  2. 方法返回值直接写入响应体

这两个动作打包在一起。

例如:

java
/**
 * 用户查询接口示例。
 */
@RestController
@RequestMapping("/users")
public class UserController {

    /**
     * 按用户 ID 查询用户信息。
     *
     * @param id 用户主键 ID
     * @return 查询到的用户视图对象
     */
    @GetMapping("/{id}")
    public UserVO getById(@PathVariable Long id) {
        return new UserVO(id, "tom");
    }
}

如果不用 @RestController,通常要这样写:

java
/**
 * 使用 `@Controller + @ResponseBody` 暴露 JSON 接口的控制器示例。
 */
@Controller
@ResponseBody
public class UserController {
}

8. 参数绑定到底在帮你做什么

Spring MVC 一个很重要的价值,是把请求里的不同参数来源统一绑定到方法参数上。

例如常见的:

  1. @PathVariable
  2. @RequestParam
  3. @RequestBody
  4. @RequestHeader

它们分别在解决不同来源的数据怎么进入方法。

例如:

java
/**
 * 演示 Spring MVC 如何把路径参数和查询参数同时绑定到方法参数上。
 *
 * @param id 路径里的用户 ID
 * @param name 查询参数里的用户名称,允许不传
 * @return 这里为了突出参数绑定主线,示例里返回 `null`
 */
@GetMapping("/user/{id}")
public UserVO getUser(
        @PathVariable Long id,
        @RequestParam(required = false) String name) {
    return null;
}

这里最重要的理解不是注解会写,而是 Spring MVC 在帮你做请求数据到 Java 方法参数之间的映射

8.1 请求映射相关常用注解

这部分在实际开发里最常用:

注解作用
@RequestMapping通用请求映射注解
@GetMapping处理 GET 请求
@PostMapping处理 POST 请求
@PutMapping处理 PUT 请求
@DeleteMapping处理 DELETE 请求
@PatchMapping处理 PATCH 请求

这些 xxxMapping 都可以理解成 对 @RequestMapping(method = ...) 的组合封装

例如:

java
/**
 * 使用通用 `@RequestMapping` 处理 GET 请求的写法。
 *
 * @param id 路径参数中的用户 ID
 * @return 这里为了突出注解写法,示例里返回 `null`
 */
@RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
public UserVO getUser(@PathVariable Long id) {
    return null;
}

通常会写成更直接的:

java
/**
 * 使用 `@GetMapping` 处理 GET 请求的更简洁写法。
 *
 * @param id 路径参数中的用户 ID
 * @return 这里为了突出映射写法,示例里返回 `null`
 */
@GetMapping("/users/{id}")
public UserVO getUser(@PathVariable Long id) {
    return null;
}

8.2 参数绑定常用注解总表

注解作用常见场景
@PathVariable绑定路径参数/users/{id}
@RequestParam绑定查询参数或表单参数?page=1
@RequestBody绑定请求体JSON 请求
@RequestHeader绑定请求头token、traceId
@CookieValue绑定 Cookie登录态、偏好信息

再看一个更完整的接口示例:

java
/**
 * 演示一个更完整的订单更新接口参数绑定过程。
 *
 * @param orderId 路径参数里的订单 ID
 * @param dryRun 查询参数,表示这次是否只做演练不真正落库
 * @param traceId 请求头里的链路追踪 ID
 * @param request 请求体里的订单更新内容
 * @return 组装后的订单视图对象
 */
@PostMapping("/orders/{orderId}")
public OrderVO updateOrder(
        @PathVariable Long orderId,
        @RequestParam(defaultValue = "false") boolean dryRun,
        @RequestHeader("X-Trace-Id") String traceId,
        @RequestBody UpdateOrderRequest request) {
    return new OrderVO(orderId, request.getStatus(), dryRun, traceId);
}

9. HttpMessageConverter 是什么

当前后端分离项目返回 JSON 时,经常会遇到这个角色。

它可以看成 负责在 Java 对象和 HTTP 消息体之间做转换的组件

它解决的是:

  1. 请求体里的 JSON 怎么转成 Java 对象
  2. Java 返回对象怎么转成 JSON 响应

所以很多“为什么对象能自动转 JSON”的问题,背后其实就和消息转换器有关。

例如下面这个接口:

java
/**
 * 演示消息转换器如何把请求体转成对象,再把返回对象转成 JSON。
 *
 * @param request 创建用户请求体
 * @return 创建后的用户视图对象
 */
@PostMapping("/users")
public UserVO create(@RequestBody CreateUserRequest request) {
    return new UserVO(1L, request.getName());
}

它背后实际做了两件事:

  1. 把请求体 JSON 转成 CreateUserRequest
  2. 再把 UserVO 转成 JSON 响应

10. 校验和异常处理为什么也属于 MVC 主线

因为一次请求真正走完,不只是“进入 Controller 再返回”这么简单。

中间还包括:

  1. 参数校验
  2. 类型转换
  3. 异常处理
  4. 响应统一格式

10.1 参数校验

例如常见的:

  1. @Valid
  2. @Validated

它们解决的是:请求参数在进入业务逻辑之前,先做结构和规则校验。

例如:

java
/**
 * 创建用户接口的请求体对象。
 */
public class CreateUserRequest {

    @NotBlank
    private String name;

    @Min(1)
    private Integer age;

    // getter/setter
}

/**
 * 演示 `@RequestBody` 会把 JSON 请求体自动绑定成 Java 对象。
 *
 * @param request 创建用户请求体
 * @return 创建后的用户视图对象
 */
@PostMapping("/users")
public UserVO create(@Valid @RequestBody CreateUserRequest request) {
    return new UserVO(1L, request.getName());
}

10.2 统一异常处理

例如常见的:

  1. @ExceptionHandler
  2. @ControllerAdvice
  3. @RestControllerAdvice

它们解决的是:不要让每个 Controller 自己重复写异常兜底逻辑。

例如:

java
/**
 * 统一处理控制层抛出的业务异常。
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理参数非法异常,并返回 400 响应。
     *
     * @param ex 当前捕获到的非法参数异常
     * @return 带有错误信息的 `400 Bad Request` 响应
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException ex) {
        return ResponseEntity.badRequest().body(ex.getMessage());
    }
}

11. Spring MVCSpring Boot 是什么关系

这也是非常常见的混淆点。

  1. Spring MVC 是 Web 框架
  2. Spring Boot 是更高层的工程化体系

Spring Boot 经常把 Spring MVC 用更省心的方式自动配置起来,但它们不是一回事。

例如你写:

  1. @RestController
  2. @GetMapping
  3. 自动返回 JSON

这些底层大多还是在用 Spring MVC 的能力,只是 Boot 帮你把环境搭好了。


12. 工程上最常见的几个误区

12.1 误区一:Spring MVC 就是写 Controller

Controller 只是入口层的一部分。

真正的主线还包括:

  1. 请求分发
  2. 参数绑定
  3. 消息转换
  4. 异常处理
  5. 响应输出

12.2 误区二:前后端分离以后,MVC 就没意义了

不是。

只是 View 的角色变轻了,Spring MVC 仍然在负责请求分发和响应组织。

12.3 误区三:返回 JSON 是 Spring Boot 的能力

这是 Spring MVC 的 Web 层处理能力,Boot 更多是在自动配置它。


13. 一句话总结

Spring MVC 的核心价值,是把 HTTP 请求从接入、分发、参数绑定、方法调用到响应输出的整条链路统一组织起来,让 Java Web 开发从零散 Servlet 编程,变成结构清晰的控制层模型。

基于 VitePress 构建的个人技术笔记。