Spring MVC 的请求链路

Spring MVC 的请求链路

Spring MVC 做的事,是把一次 HTTP 请求翻译成一次 Java 方法调用,再把返回值翻译成 HTTP 响应。理解这条链路,比记住几个注解更重要。

一、DispatcherServlet 是入口

Spring MVC 的核心不是 @RestController,而是 DispatcherServlet。它接住请求,再交给后面的组件处理。

一次请求大致经过这些步骤:

  1. HandlerMapping 根据 URL、HTTP 方法、请求头等条件找到处理方法。
  2. HandlerAdapter 负责调用这个方法,并把参数准备好。
  3. HandlerMethodArgumentResolver 解析 @PathVariable、@RequestParam、@RequestBody 等参数。
  4. Controller 执行完,返回值交给 HandlerMethodReturnValueHandler。
  5. 返回值需要写 JSON 时,由 HttpMessageConverter 完成序列化;需要页面时,由 ViewResolver 找到视图。

注解的作用是把方法登记进这条链路,而不是替代链路。@GetMapping 不会自己接请求,真正接请求的还是 DispatcherServlet。

请求进入 DispatcherServlet 后,主要组件按下面的顺序协作。

从 Filter 到 MessageConverter,每一段都在处理同一件事的不同侧面:请求如何进入、方法如何被选中、参数如何绑定、响应如何写出。

二、一个最小请求

java 复制代码
// 示意:Controller 只负责 HTTP 参数和返回值
@RestController
@RequestMapping("/orders")
class OrderController {

    private final OrderService orderService;

    OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @GetMapping("/{id}")
    Order get(@PathVariable Long id) {
        return orderService.get(id);
    }
}

这段代码把路径里的 id 交给 OrderService,Controller 不碰业务规则。@RestController 等于 @Controller 加 @ResponseBody,所以返回值会直接写进响应体。

如果换成 @Controller,返回的字符串默认会被当成视图名。想直接返回数据,就要在方法或类上补 @ResponseBody。这个差别经常导致"接口返回了一个页面名",而不是 JSON。

三、参数和返回值

常见参数来源可以这样分:

  • @PathVariable 取路径变量,例如 /orders/42 里的 42。
  • @RequestParam 取查询参数,例如 ?page=2。
  • @RequestBody 取请求体,通常配合 JSON 使用。
  • @RequestHeader 取请求头。

@RequestBody 背后会读取请求输入流。这个流通常只能读一次,所以不要在过滤器或拦截器里先读一遍,再让 Controller 读第二遍。前后两处都想拿请求体时,应该先包装成可重复读取的请求对象。

返回值处理也要看上下文。返回对象并加上 @ResponseBody 时,Spring 会寻找能把对象写成 JSON 的消息转换器;类路径里有 Jackson 时,通常会用它。返回 ResponseEntity 可以同时控制状态码、响应头和响应体,适合需要精确控制 HTTP 响应的接口。

四、404、400、415

接口报错时,先按 HTTP 语义定位:

现象 常见原因 先查什么
404 URL 没有匹配到处理方法 映射路径、请求方法、组件扫描范围
400 参数类型转换失败、校验失败 @PathVariable / @RequestParam 类型、校验注解
415 请求体格式和接口要求不一致 Content-Type、@RequestBody、消息转换器
500 Controller 或 Service 内部异常 异常栈、全局异常处理、下游依赖

404 不一定是 Controller 没写对。Controller 所在的包如果不在启动类的扫描范围里,映射根本不会注册。415 也不一定是 JSON 写错,先确认请求头是不是 application/json,再看消息转换器有没有加载。

五、Controller 的边界

Controller 的职责是协议适配:接参数、调用 Service、组织响应。业务规则不要塞进来,事务也不要直接开在 Controller 里。

java 复制代码
// 示意:把业务规则留在 Service
@RestController
@RequestMapping("/orders")
class OrderController {

    private final OrderService orderService;

    OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @PostMapping
    ResponseEntity<Order> create(@RequestBody CreateOrderRequest request) {
        Order order = orderService.create(request);
        return ResponseEntity.status(201).body(order);
    }
}

这段代码只做参数接收和响应状态设置。订单能不能创建、库存怎么扣、价格怎么算,都属于 Service。

Filter、Interceptor 和 Controller 也不在同一层。Filter 在 Servlet 容器层处理请求,Interceptor 在 Spring MVC 找到 Handler 之后执行,Controller 才是具体的方法。登录校验、编码、跨域这类通用逻辑,先判断应该放在哪一层,不要全堆进 Controller。

六、排错顺序

遇到接口问题时,可以按这个顺序走:

  1. 请求有没有进入 DispatcherServlet。
  2. HandlerMapping 有没有找到方法。
  3. 参数解析器有没有拿到正确的值。
  4. Controller 有没有正常调用 Service。
  5. 返回值有没有被正确的消息转换器写出。

404 先查映射和扫描范围,415 先查 Content-Type 和消息转换器,400 先查参数绑定。把问题留在 MVC 层解决,不要一看到接口失败就去翻 AOP。

相关推荐
拆房老料1 小时前
【第四部分:Agentic 智能体设计模式】17. Agent 路由模式详解:从意图识别到动态模型选择
java·设计模式·ai编程·策略模式
樱花落木兰1 小时前
Redis 消息队列的可靠性演进:从 List、Pub/Sub 到 Stream 消费者组,以及 8.x 版本改变了什么
java·开发语言·经验分享·redis·分布式·python·软件工程
wtblszn1 小时前
凹版印刷机智慧运维物联网系统方案
java·运维·物联网
Nuanyt1 小时前
LeetCode Hot 100 链表专题(四)· 3 题(Java)
java·算法·leetcode·链表·双指针
zhongben_keji2 小时前
本地生活 APP 开发:城市 × 品类 × 履约形态的三维网格与三套账
java·大数据·生活
code2cat3 小时前
Java工具篇之Maven:从pom.xml到测试通过的JAR
xml·java·maven
wuminyu10 小时前
JVM规范学习总结:解释器与JIT编译器如何处理JVM字节码
java·linux·c语言·jvm·c++
txxzjmzlh10 小时前
SpringBoot 配置⽂件
java·spring boot·spring
Wang's Blog10 小时前
Java 项目实战: 交友项目 - 搜索附近的功能概述和分析
java·开发语言·交友