瀏覽器發(fā)給服務(wù)端的是一個(gè)HTTP格式的請(qǐng)求,HTTP服務(wù)器收到這個(gè)請(qǐng)求后,需要調(diào)用服務(wù)端程序來(lái)處理,所謂的服務(wù)端程序就是你寫(xiě)的java類(lèi),一般來(lái)說(shuō)不同的請(qǐng)求需要不同的java類(lèi)來(lái)處理.
那么問(wèn)題來(lái)了,HTTP服務(wù)器怎么知道要帶哦用哪個(gè)java類(lèi)的哪個(gè)方法呢?最直接的作法就是在HTTP服務(wù)器里寫(xiě)一堆if else邏輯判斷:如果是A請(qǐng)求就調(diào)用X類(lèi)的M1方法,如果是B請(qǐng)求就調(diào)用Y類(lèi)的M2方法.但這樣做明顯由問(wèn)題,因?yàn)镠TTP服務(wù)器的代碼跟業(yè)務(wù)邏輯耦合在一起了,如果新加一個(gè)業(yè)務(wù)方法還要修改HTTP服務(wù)器的代碼.
那該怎么解決這個(gè)問(wèn)題呢?我們知道,面向接口編程是解決耦合的法寶,于是又一伙人就定義了一個(gè)接口,各種業(yè)務(wù)類(lèi)都必須實(shí)現(xiàn)這個(gè)接口,這個(gè)接口就叫Servlet接口,有時(shí)我們也把實(shí)現(xiàn)了Servlet接口的業(yè)務(wù)類(lèi)叫Servlet.
但是這里還有一個(gè)問(wèn)題,對(duì)于特定的請(qǐng)求,HTTP服務(wù)器如何知道由哪個(gè)Servlet來(lái)處理呢?Servlet又是由誰(shuí)來(lái)實(shí)例化呢?顯然HTTP服務(wù)器不適合這個(gè)工作,否則又和業(yè)務(wù)類(lèi)耦合了.
于是,還是那伙人又發(fā)明了Servlei容器,Servlet容器用來(lái)加載和管理業(yè)務(wù)類(lèi).HTTP服務(wù)器不直接跟業(yè)務(wù)類(lèi)打交道,而是把請(qǐng)求交給Servlet容器去處理,Servlet容器會(huì)將請(qǐng)求轉(zhuǎn)發(fā)到具體的Servlet,如果這個(gè)Servlet還沒(méi)創(chuàng)建,就加載并實(shí)例化這個(gè)Servlet,然后調(diào)用這個(gè)Servlet的接口方法,因此Servlet接口其實(shí)是Servlet容器跟業(yè)務(wù)類(lèi)之間的接口,下面我們通過(guò)一張圖來(lái)加深理解.
圖的左邊表示HTTP服務(wù)器直接調(diào)用具體的業(yè)務(wù)類(lèi),他們是緊耦合的.再看圖的右邊,HTTP服務(wù)器不直接調(diào)用業(yè)務(wù)類(lèi),而是把請(qǐng)求交給容器處理,容器通過(guò)Servlet接口調(diào)用業(yè)務(wù)類(lèi).因此Servlet接口和Servlet容器的出現(xiàn),達(dá)到了HTTP服務(wù)器與業(yè)務(wù)類(lèi)解耦的目的.
而Servlet接口和Servlet容器這一整套規(guī)范叫做Servlet規(guī)范.Tomcat和Jetty都按照Servlet規(guī)范的要求實(shí)現(xiàn)了Servlet容器,同時(shí)他們也具有HTTP服務(wù)器的功能,作為Java程序員,如果我們要實(shí)現(xiàn)新的業(yè)務(wù)功能,只需要實(shí)現(xiàn)一個(gè)Servlet,并把它注冊(cè)到Tomcat(Servlet容器) 中,剩下的事情就由Tomcat幫我們處理了.
接下來(lái)我們看看Servlet接口具體是怎么定義的,以及Servlet規(guī)范又有哪些要重點(diǎn)關(guān)注的地方呢?
Servlet接口
Servlet 接口定義了下面五個(gè)方法:
public interface Servlet {
? ? void init(ServletConfig config) throws ServletException;
? ? ServletConfig getServletConfig();
? ? void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;
? ? String getServletInfo();
? ? void destroy();
}
其中最重要的是service方法,具體業(yè)務(wù)類(lèi)在這個(gè)方法里實(shí)現(xiàn)處理邏輯.這個(gè)方法有兩個(gè)參數(shù):ServletRequest和ServletResponse,ServletRequest用來(lái)封裝請(qǐng)求信息,ServletResponse用來(lái)封裝響應(yīng)信息,因此本質(zhì)上這兩個(gè)類(lèi)是對(duì)通信協(xié)議的封裝.
比如HTTP協(xié)議中的請(qǐng)求和響應(yīng)就是對(duì)應(yīng)了HttpServletRequest和HttpServletResponse這兩個(gè)類(lèi).你可以通過(guò)HttpServletRequest來(lái)獲取所有請(qǐng)求相關(guān)的信息,包括請(qǐng)求路徑,Cookie,HTTP頭,請(qǐng)求參數(shù)等,此外,我們還可以通過(guò)HttpServletRequest來(lái)創(chuàng)建和獲取Session,而HttpServletResponse是用來(lái)封裝HTTP響應(yīng)的.
你可以看到接口中還有兩個(gè)跟聲明周期有關(guān)的方法init和destory,這是一個(gè)比較貼心的設(shè)計(jì),Servlet容器在加載Servlet類(lèi)的時(shí)候會(huì)調(diào)用init方法,在卸載的時(shí)候會(huì)調(diào)用destory方法,我們可能會(huì)在init方法里初始化一些資源,并在destory方法里釋放這些資源,比如Spring MVC中的DispatcherServlet,就是在init方法里創(chuàng)建了自己的Spring容器.
你還會(huì)注意到ServletConfig這個(gè)類(lèi),ServletConfig的作用就是封裝Servlet的初始化參數(shù),你可以在web.xml給Servlet參數(shù),并在程序里通過(guò)getServletConfig方法拿到這些參數(shù),
我們知道,有接口一般就有抽象類(lèi),抽象類(lèi)用來(lái)實(shí)現(xiàn)接口和封裝通用的邏輯,因此Servlet規(guī)范提供了GenericServlet抽象類(lèi),我們可以通過(guò)擴(kuò)展它來(lái)實(shí)現(xiàn)Servlet.雖然Servlet規(guī)范并不在乎通信協(xié)議是什么,但是大多數(shù)的Servlet都是在HTTP環(huán)境中處理的,因此Servlet規(guī)范還提供了HttpServlet來(lái)繼承GenericServlet,并且加入了HTTP特性,這樣我們通過(guò)繼承HttpServlet類(lèi)來(lái)實(shí)現(xiàn)自己的Servlet,只需要重寫(xiě)兩個(gè)方法:doGet和doPost.
Servlet容器
前面提到,為了解耦,HTTP服務(wù)器不直接調(diào)用Servlet,而是把請(qǐng)求交給Servlet容器來(lái)處理,那Servlet容器又是怎么工作的呢?接下來(lái)我會(huì)介紹Servlet容器大體的工作流程,一起來(lái)聊聊我們關(guān)心的兩個(gè)話(huà)題:Web應(yīng)用的目錄格式是什么樣的,以及我該怎樣擴(kuò)展和定制化Servlet容器的功能.
工作流程
當(dāng)客戶(hù)請(qǐng)求某個(gè)資源時(shí),HTTP服務(wù)器會(huì)用一個(gè)ServletRequest對(duì)象把客戶(hù)的請(qǐng)求信息封裝起來(lái),然后調(diào)用Servlet容器的service方法,Servlet容器拿到請(qǐng)求后,根據(jù)請(qǐng)求的URL和Servlet的映射關(guān)系,找到相應(yīng)的Servlet,如果Servlet還沒(méi)有被加載,就用反射機(jī)制創(chuàng)建這個(gè)Servlet,并調(diào)用Servlet的init方法來(lái)完成初始化,接著調(diào)用Servlet的service方法來(lái)處理請(qǐng)求,把ServletResponse對(duì)象返回給HTTP服務(wù)器,HTTP服務(wù)器會(huì)把響應(yīng)發(fā)送給客戶(hù)端,同樣我通過(guò)一張圖來(lái)幫助你理解;
Web應(yīng)用
Servlet容器會(huì)實(shí)例化和調(diào)用Servlet,那Servlet是怎么注冊(cè)到Servlet容器中的呢?一般來(lái)說(shuō),我們是以Web應(yīng)用程序的方式來(lái)部署Servlet的,而根據(jù)Servlet規(guī)范,Web應(yīng)用程序有一定的目錄結(jié)構(gòu),在這個(gè)目錄下分別放置了Servlet的類(lèi)文件,配置文件以及靜態(tài)資源,Servlet容器通過(guò)讀取配置文件,就等找到并加載Servlet.Web應(yīng)用的目錄結(jié)構(gòu)大概是下面這樣:
| - MyWebApp
? ? ? | -? WEB-INF/web.xml? ? ? ? -- 配置文件,用來(lái)配置 Servlet 等
? ? ? | -? WEB-INF/lib/? ? ? ? ? -- 存放 Web 應(yīng)用所需各種 JAR 包
? ? ? | -? WEB-INF/classes/? ? ? -- 存放你的應(yīng)用類(lèi)邀窃,比如 Servlet 類(lèi)
? ? ? | -? META-INF/? ? ? ? ? ? ? -- 目錄存放工程的一些信息
Servlet規(guī)范里定義了ServletContext這個(gè)接口來(lái)對(duì)應(yīng)一個(gè)Web應(yīng)用,Web應(yīng)用部署好后,Servlet容器在啟動(dòng)時(shí)會(huì)加載Web應(yīng)用,并為每個(gè)Web應(yīng)用創(chuàng)建唯一的ServletContext對(duì)象.你可以把ServletContext堪稱(chēng)時(shí)一個(gè)全局對(duì)象,一個(gè)Web應(yīng)用可能有多個(gè)Servlet,這些Servlet可以通過(guò)全局的ServletContext來(lái)共享數(shù)據(jù),這些數(shù)據(jù)包括Web應(yīng)用的初始化參數(shù),Web應(yīng)用目錄下的文件資源等,由于Servlet Context持有所有Servlet實(shí)例,你還可以通過(guò)它來(lái)實(shí)現(xiàn)Servlet請(qǐng)求的轉(zhuǎn)發(fā).
擴(kuò)展機(jī)制
不知道你有沒(méi)有發(fā)現(xiàn),引入了Servlet規(guī)范后,你不需要關(guān)心Socket網(wǎng)絡(luò)通信,不需要關(guān)心HTTP協(xié)議,也不需要關(guān)心你的業(yè)務(wù)類(lèi)時(shí)如何被實(shí)例化和調(diào)用的.因?yàn)檫@些都被Servlet規(guī)范標(biāo)準(zhǔn)化了,你只關(guān)心怎么實(shí)現(xiàn)你的業(yè)務(wù)邏輯,這對(duì)程序員來(lái)說(shuō)是件好事,但也有不方便的一面,所謂規(guī)范就是說(shuō)大家都要遵守,就會(huì)千篇一律,但是如果這個(gè)規(guī)范不能滿(mǎn)足你的業(yè)務(wù)的個(gè)性化需求,就有問(wèn)題了,因此設(shè)計(jì)一個(gè)規(guī)范或者一個(gè)中間件,要充分考慮可擴(kuò)展性,Servlet規(guī)范提供了兩種擴(kuò)展機(jī)制:Filter和Listener.
Filter是過(guò)濾器,這個(gè)接口允許你對(duì)請(qǐng)求和響應(yīng)做一些統(tǒng)一的定制化處理,比如你可以根據(jù)請(qǐng)求的頻率來(lái)限制訪(fǎng)問(wèn),或者根據(jù)國(guó)家地區(qū)的不同來(lái)修改響應(yīng)內(nèi)容,過(guò)濾器的工作原理是這樣的:Web應(yīng)用部署完成后,Servlet容器需要實(shí)例化Filter并把Filter鏈接成一個(gè)FilterChain,當(dāng)請(qǐng)求進(jìn)來(lái)時(shí),獲取第一個(gè)Filter調(diào)用doFilter方法,doFilter方法負(fù)責(zé)調(diào)用這個(gè)FilterChain中的下一個(gè)Filter.
Listener是監(jiān)聽(tīng)器,這是另一種擴(kuò)展機(jī)制 ,當(dāng)web應(yīng)用在Servlet容器中運(yùn)行時(shí),Servlet容器內(nèi)部會(huì)不斷的發(fā)生各種事件,如web應(yīng)用的啟動(dòng)和停止,用戶(hù)請(qǐng)求到達(dá)等,Servlet容器提供了一些默認(rèn)的監(jiān)聽(tīng)器來(lái)監(jiān)聽(tīng)這些事件,當(dāng)事件發(fā)生時(shí),Servlet容器會(huì)負(fù)責(zé)調(diào)用監(jiān)聽(tīng)器的方法.當(dāng)然,你可以定義自己的監(jiān)聽(tīng)器去監(jiān)聽(tīng)你感興趣的事件,將監(jiān)聽(tīng)器配置在web.xml中,比如Spring就實(shí)現(xiàn)了自己的監(jiān)聽(tīng)器,來(lái)監(jiān)聽(tīng)ServletContext的啟動(dòng)事件,目的是當(dāng)Servlet容器啟動(dòng)時(shí),創(chuàng)建并初始化全局的Spring容器.
本期精華
今天我們學(xué)習(xí)了什么是Servlet,回顧一下,Servlet本質(zhì)上是一個(gè)接口,實(shí)現(xiàn)了Servlet接口的業(yè)務(wù)類(lèi)也叫Servlet.Servlet接口其實(shí)是Servlet容器跟具體Servlet業(yè)務(wù)類(lèi)之間的接口,Servlet接口跟Servlet容器這整套規(guī)范叫做Servlet規(guī)范,而Servlet規(guī)范使得程序員可以專(zhuān)注業(yè)務(wù)邏輯的開(kāi)發(fā),同時(shí)Servlet規(guī)范也給開(kāi)發(fā)者提供了擴(kuò)展的機(jī)制Filter和Listener.
Filter是干預(yù)過(guò)程的,它是過(guò)程的一部分,是基于過(guò)程行為的.
Listener是基于狀態(tài)的,任何行為改變同一個(gè)狀態(tài),觸發(fā)的事件是一致的.