当前位置: 首页 > news >正文

解密 Spring MVC:从 Tomcat 到 Controller 的一次完整请求之旅

今天,想和你聊一个我们每天都在打交道,但可能不曾深入思考的话题:当一个 HTTP 请求从浏览器发出,到最终被我们的 Spring Controller 处理,它到底经历了一场怎样的旅程?

理解这个流程,不仅仅是为了应付面试,更是为了在遇到棘手问题时,能像庖丁解牛一样,精准定位问题所在。这趟旅程,我们可以清晰地划分为两大站:Tomcat 处理阶段Spring MVC 处理阶段


第一站:Tomcat 的守门与引导

在请求进入 Spring 的世界之前,Tomcat 作为"前哨站",需要完成一系列的接待和引导工作。

1. 门口的接待员:Connector

当一个请求,比如 http://localhost:8080/user/info,敲响 8080 端口的大门时,Tomcat 的 Connector 组件第一个站出来迎接。它的职责就是监听网络端口,接收原始的 TCP 连接,并将其解析成一个标准的 HttpServletRequest 对象。

同时,为了高效处理并发,Tomcat 会从一个线程池(比如 http-nio-8080-exec-1)中取出一个工作线程,专门为这个请求服务,直到响应完成。

2. 容器的层层路由

请求对象创建好后,就进入了 Tomcat 的容器内部。这个过程就像一个俄罗斯套娃,请求会依次经过 EngineHostContextWrapper 这几层。

// 伪代码:感受一下这个调用链
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
  • Engine: 全局引擎,服务于整个 Tomcat 实例。
  • Host: 虚拟主机,对应一个域名,比如 localhost
  • Context: Web 应用,对应我们的项目。Tomcat 在这里根据 / 之后的 URL 路径,匹配到处理该路径的 Context
  • Wrapper: Servlet 包装器。最终,Context 会根据 web.xml 中配置的 servlet-mapping,找到处理这个请求的最终 Servlet。在 Spring Boot 应用中,这个 Servlet 通常就是大名鼎鼎的 DispatcherServlet

3. 第一道安检:过滤器链 (Filter Chain)

在请求被正式交给 DispatcherServlet 之前,它必须先通过一系列"安检"——这就是过滤器(Filter)

过滤器是 Servlet 规范的一部分,像一道道关卡,按顺序执行。

// 过滤器链执行伪代码
// 只有当所有 Filter 都放行,请求才会最终到达 Servlet
for (Filter filter : filters) {filter.doFilter(request, response, chain);
}
// 链的末端,触发 Servlet 的 service 方法
chain.doFilter(request, response);

实战场景:

  • CharacterEncodingFilter: 确保全站请求和响应的字符集统一,防止乱码。
  • CorsFilter: 解决跨域问题,允许特定来源的前端应用访问。
  • 自定义的 JwtAuthFilter: 对受保护的 API 进行身份验证,解析 Token,并将用户信息存入 SecurityContext
  • LoggingFilter: 记录所有请求的详细日志,便于审计和调试。

只有通过了所有过滤器的"盘问",请求才算完成了在 Tomcat 阶段的旅程,正式敲响了 Spring MVC 的大门。


第二站:Spring MVC 的调度中心 - DispatcherServlet

DispatcherServlet 是 Spring MVC 的绝对核心,堪称"中央调度员"。它接管请求后,会在其 doDispatch 方法内, orchestrate(精心安排)后续所有操作。

// DispatcherServlet.doDispatch 精简核心逻辑
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {// 1. 根据请求查找 Handler(即 Controller 方法)HandlerExecutionChain mappedHandler = getHandler(request);// 2. 获取能执行这个 Handler 的适配器 HandlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 3. 执行拦截器的 preHandle() 方法,这是进入 Controller 前的最后一道关卡if (!mappedHandler.applyPreHandle(request, response)) {return; // 如果 preHandle 返回 false,请求被中断}// 4. 真正调用 Controller 方法ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());// 5. 执行拦截器的 postHandle() 方法mappedHandler.applyPostHandle(request, response, mv);// 6. 处理派发结果(如渲染视图或处理异常)processDispatchResult(request, response, mappedHandler, mv, null);
}

让我们一步步拆解这个过程:

1. HandlerMapping:找到对的人

DispatcherServlet 首先会询问 HandlerMapping:“嘿,这个 URL (/user/info) 应该由哪个 Controller 的哪个方法来处理?”。
RequestMappingHandlerMapping 会扫描所有被 @RequestMapping@GetMapping 等注解标记的方法,构建一个 URL 与 HandlerMethod 的映射关系,然后精准地找到匹配项。

2. Interceptor preHandle:Controller 前的最后机会

找到目标 Controller 方法后,并不会立刻执行。而是先执行所有匹配该路径的**拦截器(Interceptor)**的 preHandle 方法。

这是一个关键的切入点。preHandle 返回 true 则放行,返回 false 则请求被直接中断。

// 拦截器 preHandle 示例
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 比如,进行更细粒度的权限校验if (!checkAuth(request, handler)) { // 甚至可以拿到 handler 信息做更复杂的判断response.sendError(403, "权限不足");return false; // 中断请求}return true; // 放行
}

3. 参数解析与 Controller 方法执行

通过了所有拦截器的 preHandle 后,HandlerAdapter 开始工作。它会借助 HandlerMethodArgumentResolver 等一系列"参数解析器",神奇地将 HTTP 请求中的各种信息(如 @RequestBody 的 JSON、@RequestParam 的查询参数、@PathVariable 的路径变量)转换并注入到你 Controller 方法的参数列表中。

然后,通过反射,你的 Controller 方法终于被执行了!

4. AOP 切面:无感知的逻辑增强

就在你的 Controller 方法执行前后,AOP(面向切面编程)可能会"神不知鬼不觉"地介入。如果你的方法上加了 @Transactional@Cacheable 或是自定义的 AOP 注解,那么相关的切面逻辑(如环绕通知)会在这里执行。

// 环绕通知示例:在 Controller 方法执行前后织入逻辑
@Around("@annotation(com.example.MyCustomLog)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {long start = System.currentTimeMillis();// 执行 Controller 方法Object result = joinPoint.proceed();long duration = System.currentTimeMillis() - start;log.info("{} 执行耗时: {} ms", joinPoint.getSignature(), duration);return result;
}

AOP 的美妙之处在于,它让你的业务代码保持纯净,同时又能附加额外的通用功能。

5. Interceptor postHandle & 视图渲染

Controller 方法执行完毕,并返回了一个结果(比如一个 ModelAndView 对象或者一个被 @ResponseBody 标记的对象)。
此时,拦截器的 postHandle 方法会被调用。你可以在这里对 ModelAndView 进行修改,或者在响应提交前做一些额外操作。

如果返回的是 ModelAndViewDispatcherServlet 会通过 ViewResolver(视图解析器)找到对应的视图模板(如 Thymeleaf 或 JSP),并用模型数据进行渲染,最终生成 HTML 响应。

6. Interceptor afterCompletion:最后的清理工作

无论请求处理过程中是否发生异常,只要它经过了拦截器的 preHandle 并返回 true,那么在整个请求完成(视图渲染完毕或响应已提交)后,拦截器的 afterCompletion 方法就一定会被调用。

这里是执行资源清理工作的最佳地点,比如清理线程绑定的变量等。


全景图:一张图看懂执行顺序

为了更直观地理解整个流程,我为你绘制了一张流程图:

响应
Spring MVC 框架
Tomcat 服务器
客户端
1. 查找 Handler
2. 获得 HandlerExecutionChain
3. 放行
4. 调用
5. 返回 ModelAndView/结果
6. 视图处理
7. 响应完成后
返回 HTTP 响应
DispatcherServlet
HandlerMapping
拦截器 preHandle
参数解析/AOP
Controller 方法执行
拦截器 postHandle
视图渲染 ViewResolver
拦截器 afterCompletion
Connector 监听端口
线程池分配线程
Tomcat 容器路由
过滤器链 Filter Chain
发起 HTTP 请求

调试技巧:在 DispatcherServletdoDispatch 方法里打上一个断点,然后单步调试。你会清晰地看到 getHandlerapplyPreHandleha.handle 等关键步骤的调用过程,这是理解整个流程最快的方式。


实战排雷:常见问题与调试技巧

1. 灵魂拷问:Filter vs. Interceptor?

这是个老生常谈但至关重要的问题。

特性过滤器 (Filter)拦截器 (Interceptor)
出身Servlet 规范,J2EE 标准,任何 Web 框架都能用Spring MVC 框架特有,高度集成于 Spring 上下文
执行时机DispatcherServlet 之前,无法触及 ControllerDispatcherServlet 之后,Controller 执行前后
依赖注入默认不支持 @Autowired,需特殊配置(如 FilterRegistrationBean由 Spring IoC 容器管理,可直接 @Autowired 注入任何 Bean
能力范围能处理所有进入 Tomcat 的请求,包括静态资源只能拦截进入 DispatcherServlet 的请求
获取信息无法直接获取即将执行的 Controller 方法信息可以获取 HandlerMethod,知道具体是哪个方法在处理

一句话总结Filter 是粗粒度的全局"门卫",适合做认证、编码等通用工作;Interceptor 是细粒度的"警卫",适合做权限、日志等与业务逻辑相关的校验。

2. 为何静态资源不经过我的拦截器?

因为 Spring Boot 默认配置下,对于 /static/public 等目录下的静态资源请求,会由一个名为 DefaultServletHttpRequestHandler 的处理器直接处理,它会绕过 DispatcherServlet,直接将资源以流的形式返回。因此,你的拦截器自然也就不会被触发。

3. 如何优雅地跳过某些路径的拦截器?

在配置拦截器时,使用 excludePathPatterns 方法。

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new MyAuthInterceptor()).addPathPatterns("/**") // 拦截所有.excludePathPatterns("/login", "/error", "/static/**"); // 排除特定路径}
}

4. 如何捕获全局异常?

使用 @ControllerAdvice@ExceptionHandler 的组合拳,可以优雅地处理全局异常,避免 try-catch 遍地开花。

@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)public ResponseEntity<String> handleGenericException(Exception e) {// 记录日志log.error("系统发生未知异常", e);// 返回一个对用户友好的错误信息return ResponseEntity.status(500).body("服务器开小差了,请稍后再试~");}@ExceptionHandler(IllegalArgumentException.class)public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e) {return ResponseEntity.status(400).body("请求参数不合法: " + e.getMessage());}
}

总结:

好了,这次从 Tomcat 到 Controller 的请求之旅就到这里。我们一起梳理了其中的每一个关键节点和核心组件。

掌握这条核心路径,你就能:

  1. 清晰定位问题:到底是 Filter 拦了,还是 Interceptor 没过?是参数解析错了,还是 AOP 出了异常?
  2. 优雅设计系统:合理地在 Filter、Interceptor、AOP、ControllerAdvice 中放置你的逻辑,让代码结构更清晰,职责更分明。
  3. 提升性能:理解了流程,才能更好地进行性能分析和优化。

这次的深度剖析,能让我们都对 Spring MVC 的请求处理有更深刻的理解,也是日常学习的一个记录📝。

相关文章:

  • Java求职者面试:Spring AI、MCP、RAG、向量数据库与Embedding模型技术解析
  • 世事无两全
  • 串口DMA + 环形缓冲,共用缓冲区
  • 使用Trace分析Android方法用时
  • Pyenv 跟 Conda 还有 Poetry 有什么区别?各有什么不同?
  • 在使用 HTML5 的 <video> 标签嵌入视频时,有时会遇到无法播放 MP4 文件的问题
  • python打卡day54@浙大疏锦行
  • 黑马教程强化day5-2
  • 【Node.js 的底层实现机制】从事件驱动到异步 I/O
  • 什么是Flink
  • 红队攻防渗透技术实战流程:信息打点-主机架构蜜罐识别WAF识别端口扫描协议识别服务安全
  • Uniapp H5端SEO优化全攻略:提升搜索引擎排名与流量
  • JVM 内存模型与垃圾回收机制全解析:架构、算法、调优实践
  • Minio 基于 bearer_token 监控
  • 【AI作画】用comfy ui生成漫画风图画
  • python调用 powershell 执行dir 并获取每行的length列属性值
  • 【数据分析九:Association Rule】关联分析
  • 【前端AI实践】DeepSeek:开源大模型的使用让开发过程不再抓头发
  • 打造高效工作环境:技术方案助力文件整理提速
  • C++ 进阶:深入理解虚函数、继承与多态
  • 昌平知名的网站制作建设报价/企业网站网页设计
  • 网站302跳转/国外网页模板
  • 电商网站建设与运行/公司网站费用
  • 有哪些做企业点评的网站/百度一下你就知道了百度一下
  • 成套小说网站模板/怎么学seo基础
  • 在线电子印章生成器/seo网络推广课程