解密 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 的請求處理有更深刻的理解,也是日常學習的一個記錄📝。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處: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;廣泛應用于需要高可用性、分…