Java 后端请求链路:从 HTTP 到数据库

刚开始学 Spring Boot 时,常见的写法是给方法加上 @GetMapping,然后返回一个对象。但一次请求从浏览器到达这个方法,中间经过了哪些组件?理解这条链路后,控制器、拦截器、参数绑定和异常处理就不再像一堆互不相关的注解。

1. 先看整体流程

1
2
3
4
5
6
7
8
9
10
11
12
13
浏览器
→ HTTP 请求
→ Servlet 容器(例如嵌入式 Tomcat)
→ Filter 链
→ DispatcherServlet
→ HandlerMapping 找到处理器
→ HandlerAdapter 调用 Controller
→ 参数绑定与校验
→ Service 处理业务
→ Mapper / MyBatis 执行 SQL
→ 返回对象
→ HttpMessageConverter 转换为 JSON
→ HTTP 响应

这是便于理解的主干。异常处理器、拦截器、安全过滤器、事务代理和序列化配置都会影响具体过程。

2. Servlet 容器和 Filter

嵌入式 Tomcat 负责监听端口、接收 HTTP 数据,并把请求交给 Servlet 体系处理。Filter 位于 Servlet 之前,常用于跨请求的通用处理,例如编码、请求日志或安全校验。Filter 可以选择继续调用后续链路,也可以直接结束请求。

Filter 并不等于 Spring MVC 拦截器。Filter 属于 Servlet 规范;Spring MVC 的 HandlerInterceptor 在 DispatcherServlet 找到具体处理器后参与执行,因此可以针对控制器处理器进行拦截。

3. DispatcherServlet 如何找到 Controller

Spring MVC 采用前端控制器模式。DispatcherServlet 接收请求后,把路径、HTTP 方法、请求参数等交给一组协作组件处理。HandlerMapping 负责寻找匹配的处理器,HandlerAdapter 负责以合适方式调用处理器。

例如:

1
2
3
4
5
6
7
8
@RestController
@RequestMapping("/users")
class UserController {
@GetMapping("/{id}")
UserView getUser(@PathVariable long id) {
return userService.findView(id);
}
}

@GetMapping("/{id}") 声明的是路由映射规则。Spring MVC 选择该方法后,会把路径中的 id 转换为 Java 参数,再调用方法。

4. 参数绑定、业务层和数据库

参数解析器会按注解和参数类型处理路径变量、查询字符串、请求体、文件等数据。JSON 请求体通常由消息转换器反序列化为 Java 对象;需要格式或范围检查时,应增加 Bean Validation,并把错误转换为清晰的客户端响应。

Controller 负责协议和输入输出,Service 负责业务规则,Mapper 负责数据访问。把职责分开便于测试和维护,也让事务边界通常落在 Service 的业务操作上。Mapper 执行 SQL 后,结果会映射为对象或集合。

5. 响应如何返回浏览器

使用 @RestController 时,方法返回值通常作为响应体处理。Spring MVC 会根据内容类型选择 HttpMessageConverter,例如将对象序列化成 JSON。状态码、响应头和响应体可以分别通过返回类型、注解或 ResponseEntity 表达。

发生异常时,请求会进入异常解析流程。项目常用 @RestControllerAdvice 统一映射业务异常和校验异常,但不要把数据库密码、SQL、堆栈等内部信息直接返回给客户端。

6. 排查请求问题的顺序

  1. 浏览器实际发送的 URL、方法、请求头和请求体是否符合接口定义?
  2. 请求是否到达应用,是否被 Filter 或安全规则拦截?
  3. 路由映射和参数转换是否成功?
  4. Controller 是否调用到正确的 Service?
  5. 事务、Mapper 和 SQL 是否按预期工作?
  6. 最终状态码、响应头和 JSON 是否正确?

建议在开发环境使用断点或结构化日志逐层观察,不要只凭浏览器显示的错误页面猜测。

7. 小结

Spring Boot 简化了 Servlet 容器和 Spring MVC 的配置,但请求处理仍然由这些组件协作完成。理解“容器接收请求、DispatcherServlet 分发、Controller 调业务、Mapper 访问数据、消息转换器生成响应”这条主线,能帮助我们更快定位接口问题。

参考:Spring Framework DispatcherServlet 文档。