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. HandlerMapping、HandlerAdapter 这些词到底在说什么 ​

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

6.1 HandlerMapping ​

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

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

6.2 HandlerAdapter ​

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

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

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

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

7. Controller 和 RestController 有什么区别 ​

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 MVC 和 Spring 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 构建的个人技术笔记。