在穿越了 Web 容器之后,HTTP 請(qǐng)求將被投送到 Web 應(yīng)用,我們繼續(xù)以 Tomcat 為例剖析后續(xù)流程。Web 容器與 Web 應(yīng)用的銜接是通過配置文件 web.xml 完成的。web.xml 是遵循 Java Servlet 標(biāo)準(zhǔn)規(guī)范的配置文件,我們通過這份配置文件定義構(gòu)成 Web 應(yīng)用的各種核心組件和初始化配置,其中包括:過濾器 Filter、監(jiān)聽器 Listener、伺服器 Servlet 等等。不同組件分別承擔(dān)不同的功能,在介紹 Web 應(yīng)用處理 HTTP 請(qǐng)求流程之前,我們照例先來了解一下這些核心組件。
創(chuàng)新互聯(lián)建站專業(yè)提供服務(wù)器托管服務(wù),為用戶提供五星數(shù)據(jù)中心、電信、雙線接入解決方案,用戶可自行在線購(gòu)買服務(wù)器托管服務(wù),并享受7*24小時(shí)金牌售后服務(wù)。
過濾器 Filter 負(fù)責(zé)對(duì) HTTP 請(qǐng)求做預(yù)處理,接著將請(qǐng)求交給 Servlet 進(jìn)行處理并生成響應(yīng),最后 Filter 再對(duì)響應(yīng)進(jìn)行后處理。從 HTTP 請(qǐng)求的處理過程來看,F(xiàn)ilter 主要參與以下幾個(gè)環(huán)節(jié):
過濾器映射 filter-mapping,用于聲明 Web 應(yīng)用將會(huì)用到的過濾器,過濾器可被映射到一個(gè) Servlet 或 URL 模式。如果將過濾器映射到一個(gè) Servlet 上,那它就作用于特定 Servlet。如果將過濾器映射到一個(gè) URL 模式,那么它將作用于任何資源,只要該資源的 URL 與 URL 模式匹配。如果對(duì)某個(gè)資源的請(qǐng)求匹配到多個(gè) Filter,那么在處理 HTTP 請(qǐng)求過程中,Tomcat 將按照過濾器映射 filter-mapping 在配置文件 web.xml 中的先后順序來執(zhí)行,在前面的先執(zhí)行,在后面的后執(zhí)行,多個(gè)過濾器 Filter 可以組成調(diào)用鏈。URL 模式匹配有三種類型規(guī)則:
encodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
encodingFilter
/* ??
監(jiān)聽器 Listener 主要用于監(jiān)聽 Application、Session、Request 等對(duì)象的變化,每當(dāng)這些對(duì)象發(fā)生變化就會(huì)回調(diào)用對(duì)應(yīng)的監(jiān)聽方法。例如:在?Servlet API?中有一個(gè)?ServletContextListener?接口,它能夠監(jiān)聽?ServletContext?對(duì)象的生命周期,實(shí)際上就是監(jiān)聽?Web?應(yīng)用的生命周期。當(dāng) Servlet?容器啟動(dòng)或終止Web?應(yīng)用時(shí),會(huì)觸發(fā) ServletContextEvent?事件,該事件由 ServletContextListener?來處理。
org.springframework.web.util.Log4jConfigListener
伺服器 Servlet 負(fù)責(zé)處理客戶端訪問動(dòng)態(tài)資源的 HTTP 請(qǐng)求,接口 javax.servlet.Servlet 定義了所有 Servlet 必須要實(shí)現(xiàn)的方法。
方法名稱 | 功能說明 |
---|---|
destroy() | 由 Servlet 容器調(diào)用,用于關(guān)閉停止 Servlet 提供的服務(wù) |
getServletConfig() | 獲取 Servlet 初始化和啟動(dòng)時(shí)參數(shù)的配置信息對(duì)象 ServletConfig |
getServletInfo() | 獲取 Servlet 的說明信息,包括:作者、版本和版權(quán)等等 |
init() | 由 Servlet 容器調(diào)用,籍由配置 ServletConfig?完成 Servlet 初始化,啟動(dòng)對(duì)外服務(wù) |
service() | 由 Servlet 容器調(diào)用,讓 Servlet 處理某個(gè) HTTP 請(qǐng)求 |
從 HTTP 請(qǐng)求的處理過程來看,伺服器 Servlet 主要參與以下幾個(gè)環(huán)節(jié):
在老兵哥的讀書年代,Web 應(yīng)用相對(duì)簡(jiǎn)單,主要是各種信息管理系統(tǒng)。當(dāng)時(shí) Spring 尚未誕生,主流技術(shù)棧是 JSP/Servlet,老兵哥我開發(fā) Web 應(yīng)用時(shí)主要編寫繼承自 HttpServlet 子類,將各種業(yè)務(wù)邏輯功能分別交由不同的 HttpServlet 子類實(shí)現(xiàn)。HttpServlet 繼承自 GenericServlet,后者實(shí)現(xiàn)了接口 Servlet。隨著數(shù)字化和互聯(lián)網(wǎng)化的不斷推進(jìn),業(yè)務(wù)系統(tǒng)變得越來越復(fù)雜,HttpServlet 子類越寫越多,web.xml 配置文件越來越復(fù)雜,這導(dǎo)致系統(tǒng)的擴(kuò)展維護(hù)越來越困難,手工作坊式的開發(fā)方法已經(jīng)跟不上業(yè)務(wù)發(fā)展的步伐了。時(shí)勢(shì)造英雄,Spring 就是在這種背景下呼之而出的,它創(chuàng)造性地發(fā)明了控制反轉(zhuǎn) IOC 和面向切面編程 AOP,極大地降低復(fù)雜度。如下面配置示例所示,整個(gè) Web 應(yīng)用只需要配置一個(gè) Servlet 就可以了,它就是 Spring 的前置分發(fā)器 DispatcherServlet:
mvc
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:mvc-servlet.xml
1
mvc
/api
如下圖所示,Web 應(yīng)用處理 HTTP 請(qǐng)求的流程主要是穿越監(jiān)聽器 Listener 和過濾器鏈 Filters,最終抵達(dá)伺服器 Servlet 的過程:
原先我們將 Web 應(yīng)用的復(fù)雜度直接暴露給了 Tomcat,現(xiàn)在 Spring 通過控制反轉(zhuǎn) IOC 和面向切面編程 AOP 等新技術(shù)接管了 Web 應(yīng)用的復(fù)雜度。如前面 Servlet 的配置示例,整個(gè) Web 應(yīng)用只需要配置 Spring 提供的前置分發(fā)器 DispatcherServlet,開發(fā)者無需再編寫和配置 HttpServlet 子類,所有業(yè)務(wù)邏輯功能將按照 Spring 的標(biāo)準(zhǔn)規(guī)范來開發(fā),從原先的編寫 Listener\Filter\Servlet 改為編寫 Spring Component,包括:Controller、Service、Repository 等。
Tomcat 在接收到某個(gè) Web 應(yīng)用的 Http 請(qǐng)求之后,它會(huì)將所有請(qǐng)求都轉(zhuǎn)交給前置分發(fā)器 DispatcherServlet,再由 DispatcherServlet 將請(qǐng)求派發(fā)給具體業(yè)務(wù)邏輯進(jìn)行處理。DispatcherServlet 就是從 HttpServlet 派生的子類,它的類圖關(guān)系如下所示:
Web 應(yīng)用架構(gòu)的演進(jìn)過程就像創(chuàng)業(yè)孵化過程,最初創(chuàng)業(yè)團(tuán)隊(duì)打造的產(chǎn)品很簡(jiǎn)單,大家采用手工作坊模式來快速打造最小化可行產(chǎn)品,這時(shí)候團(tuán)隊(duì)的組織架構(gòu)也跟產(chǎn)品一樣扁平簡(jiǎn)單。但隨著產(chǎn)品被越來越多的用戶使用,功能變得越來越復(fù)雜,接著必須引進(jìn)新架構(gòu)才能有效管理復(fù)雜度,同時(shí)團(tuán)隊(duì)規(guī)模的擴(kuò)展也需要與業(yè)務(wù)發(fā)展匹配的組織架構(gòu),這樣才能保證產(chǎn)品的不斷發(fā)展。Web 應(yīng)用架構(gòu)的演化過程跟 Tomcat 體系結(jié)構(gòu)的形成過程類似,老兵哥會(huì)經(jīng)常借助“俄羅斯套娃”這個(gè)模型來闡述架構(gòu),Web 容器和 Web 應(yīng)用這兩層的架構(gòu)原則是類似的,就像大娃娃套著小娃娃一樣。
Spring 的 IOC 容器跟 Tomcat 的 Servlet 容器類似,也是通過配置文件等方式來定義組件,然后在啟動(dòng)過程中將這些定義好的組件初始化并添加到容器當(dāng)中,后續(xù)使用時(shí)從容器查找獲取。早期 Web 應(yīng)用主要由大量開發(fā)者編寫的 Filter\Listener\Servlet 等組件構(gòu)成,這些核心組件的配置全部都通過 web.xml 配置文件來維護(hù),那么 Web 應(yīng)用和 Web 容器之間其實(shí)不是松耦合的,而引進(jìn) Spring 之后就變成只需要配置 DispatcherServlet 等少量組件了,達(dá)到了分層架構(gòu)的要求,更加有利于開發(fā)復(fù)雜的 Web 應(yīng)用。如果采用架構(gòu)的專業(yè)術(shù)語來描述,這就是經(jīng)典的分層架構(gòu)模式,層與層之間松耦合,僅通過少量的接口銜接,每層內(nèi)部高內(nèi)聚。
本文主要價(jià)值是幫助大家梳理出端到端的全流程框架,也就是我們常說的全局視角或者上帝視角。有了這個(gè)框架之后,我們可以根據(jù)自己的需要按圖索驥找相關(guān)節(jié)點(diǎn)的資料來研究學(xué)習(xí),不至于陷入細(xì)節(jié)找不到方向。當(dāng)然,考慮到我們每個(gè)人的工作學(xué)習(xí)情況不同,平時(shí)遇到的問題也不同,本文內(nèi)容無法覆蓋所有人遇到的問題,歡迎大家留言提問,也歡迎關(guān)注我的博客或公號(hào)“IT老兵哥”交流互動(dòng),我會(huì)盡力盡快解答大家提出的問題,謝謝!
本系列其他文章索引如下: