今天,想和你聊一个我们每天都在打交道,但可能不曾深入思考的话题:当一个 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 的请求处理有更深刻的理解,也是日常学习的一个记录📝。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/87913.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/87913.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/87913.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在 Java 中操作 Map时,高效遍历和安全删除数据

在 Java 中操作 Map 时&#xff0c;高效遍历和安全删除数据可以通过以下方式实现&#xff1a; 一、遍历 Map 的 4 种高效方式 1. 传统迭代器&#xff08;Iterator&#xff09; Map<String, Integer> map new HashMap<>(); map.put("key1", 5); map.pu…

力扣-136.只出现一次的数字

题目描述 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 class Solution {public i…

Go 网络编程:HTTP服务与客户端开发

Go 在标准库中内置了功能强大的 net/http 包&#xff0c;可快速构建高并发、高性能的 HTTP 服务&#xff0c;广泛应用于微服务、Web后端、API中间层等场景。 一、快速创建一个HTTP服务 示例&#xff1a;最简Hello服务 package mainimport ("fmt""net/http&quo…

【Prism】 实现注入的几个标准化步骤(相机举例)

📸 Prism 架构中如何优雅地注册和注入相机服务 在开发基于 Prism + WPF 的应用时,合理使用依赖注入(DI)可以大大提高系统的可维护性和扩展性。本文以一个多相机平台管理系统为例,展示如何通过接口、枚举、容器注册等方式,实现相机服务的灵活配置与使用。 🧩 一、定义…

vue3组件式开发示例

1&#xff0c;定义组件&#xff08;根据实际调整提交分析结果方法&#xff09; <template><!-- 分析结果上传对话框组件 --><el-dialogv-model"uploadResultDialog":title"title":width"width":before-close"handleBeforeC…

基于arm linux的bluealsa开启蓝牙A2DP和SCO录音功能

bluealsa的软件架构 #mermaid-svg-ohITacCRHItwRR1t {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ohITacCRHItwRR1t .error-icon{fill:#552222;}#mermaid-svg-ohITacCRHItwRR1t .error-text{fill:#552222;stroke:…

网页后端开发(基础3--Springboot框架)

web的服务器资源&#xff1a; 静态资源&#xff1a;服务器上存储的不会改变的数据&#xff0c;通常不会根据用户的请求而变化。比如&#xff1a;HTML、CSS、JS、图片、视频等&#xff08;负责页面展示&#xff09; 动态资源&#xff1a;服务器端根据用户请求和其他数据…

ROS通过urdf_to_graphiz对urdf和xacro文件进行结构可视化

对机器人的urdf文件进行结构可视化&#xff1a; 举例命令如下&#xff1a; urdf_to_graphiz go2_description.urdf 输出 .gv 和 .pdf文件&#xff0c;打开 pdf文件如图&#xff1a;

基于Uniapp+PHP的教育培训系统开发指南:网校源码实战剖析

在线教育日益普及的今天&#xff0c;如何快速搭建一个功能完善、体验良好的教育培训系统&#xff0c;成为众多教育机构、培训企业、个体讲师关注的焦点。与其从零开发&#xff0c;不如基于成熟框架快速部署。而UniappPHP正是当前“低成本高效率”开发网校系统的黄金组合。 本文…

键盘 AK35I Pro V2 分析

文章目录 AK35I Pro V21. MCU SN32F299SN32F299 内存映射 2. Bootloader3. TFT 135x240 1.14inch4. 键盘5. Flash PY25Q128HA6. 蓝牙 CH582F7. 扩展板8. 电池 606090 3.7V 4000mAh AK35I Pro V2 AK35I Pro V2 测评视频 键盘外壳使用卡扣固定, 外壳没有螺丝, 将外框向外翘起, 用…

11. TypeScript 工具类型

TypeScript 提供了一系列内置的“工具类型”&#xff08;Utility Types&#xff09;&#xff0c;它们是对已有类型进行变换的便捷方式。通过这些工具类型&#xff0c;开发者可以更灵活、可维护地进行类型设计&#xff0c;避免重复定义类型逻辑。 工具类型的作用主要有&#xf…

Kafka性能调优全攻略:从JVM参数到系统优化

前言 在大数据处理领域&#xff0c;Kafka以其高吞吐、高并发的特性成为消息队列的首选。然而&#xff0c;随着业务规模的扩大和数据量的激增&#xff0c;若配置不当&#xff0c;Kafka的性能和稳定性会受到严重影响。其中&#xff0c;JVM参数的调整是优化Kafka性能的关键一环&a…

HarmonyOS 5 NPU支持哪些AI框架?

以下是HarmonyOS 5 NPU支持的AI框架及适配方案&#xff0c;结合关键技术和实测数据&#xff1a; 一、原生支持框架 MindSpore Lite‌ ‌核心特性‌&#xff1a; 原生适配昇腾达芬奇架构&#xff0c;支持INT8/FP16混合量化自动算子融合优化&#xff08;实测推理速度提升3.2倍…

鸿蒙uvc预览

简单查看流程&#xff0c;如有错误请指出。 CameraNativePreview.ets--> 这里开始进入uvc_camera库 (CameraDevice.ets/CameraManager.ets) --> CameraUtils.ets--> -->CameraNativeMethods(index.d.ts文件&#xff0c;路径: uvc_camera\src\main\cpp\types\…

PHP的打印语句

文章目录 环境总结打印语句换行符括号数组&#xff08;对象&#xff09;和字符串之间的相互转换 打印语句echoprint括号print_rvar_dump 数组&#xff08;对象&#xff09;和字符串之间的转换json_encodejson_decodeimplodeexplode 环境 PHP 8.4.5 总结 如果不想看详细介绍&…

功率MOSFET的SOA曲线

功率MOSFET的SOA曲线 SOA区指的是MOSFET的安全工作区&#xff0c;英文表示为Safe Operating Area&#xff0c;是指MOSFET&#xff08;金属氧化物半导体场效应晶体管&#xff09;的安全操作范围。在线性模式运行的情况下&#xff0c;SOA特别有用。但开关模式下&#xff0c;一般…

Stacking集成BP神经网络/RF/SVM和遗传算法的煤炭配比优化

一、煤炭配比优化的问题本质与技术路线 煤炭配比需同时满足煤质指标&#xff08;灰分、挥发分、热值&#xff09;、燃烧特性&#xff08;着火温度、燃尽指数&#xff09;、经济成本等多目标优化。传统方法依赖经验公式&#xff0c;难以处理非线性关系&#xff1a; 核心难点&a…

Unity Shader开发-着色器变体(2)-定义着色器变体

一.定义着色器变体 定义一个着色器变体&#xff08;Shader Variant&#xff09;从概念和实现上讲&#xff0c;主要包括以下几个核心部分 1.使用预编译指令来声明变体关键字 关键字是驱动变体生成的“开关”。它们是简单的字符串标识符&#xff0c;用于在 Shader 代码中标记不…

sql server耗时模拟

REATE PROCEDURE SimulateDelay AS BEGINPRINT 开始耗时操作...;-- 模拟等待 5 秒WAITFOR DELAY 00:00:05;PRINT 耗时操作完成。; END 方法二&#xff1a;使用忙循环(不推荐&#xff0c;CPU 占用高) CREATE PROCEDURE SimulateBusyLoop AS BEGINDECLARE start DATETIME GETDAT…

Zookeeper的典型应用场景?

大家好&#xff0c;我是锋哥。今天分享关于【Zookeeper的典型应用场景?】面试题。希望对大家有帮助&#xff1b; Zookeeper的典型应用场景? 超硬核AI学习资料&#xff0c;现在永久免费了&#xff01; Zookeeper 是一个分布式协调服务&#xff0c;广泛应用于需要高可用性、分…