Servlet工作原理:
1、首先簡(jiǎn)單解釋一下Servlet接收和響應(yīng)客戶請(qǐng)求的過程,首先客戶發(fā)送一個(gè)請(qǐng)求,Servlet是調(diào)用service()方法對(duì)請(qǐng)求進(jìn)行響應(yīng)的,通過源代碼可見,service()方法中對(duì)請(qǐng)求的方式進(jìn)行了匹配,選擇調(diào)用doGet,doPost等這些方法,然后再進(jìn)入對(duì)應(yīng)的方法中調(diào)用邏輯層的方法,實(shí)現(xiàn)對(duì)客戶的響應(yīng)。
2、每一個(gè)自定義的Servlet都必須實(shí)現(xiàn)Servlet的接口,Servlet接口中定義了五個(gè)方法,其中比較重要的三個(gè)方法涉及到Servlet的生命周期,分別是上文提到的init(),service(),destroy()方法。
3、Servlet接口和GenericServlet是不特定于任何協(xié)議的,而HttpServlet是特定于HTTP協(xié)議的類,所以HttpServlet中實(shí)現(xiàn)了service()方法,并將請(qǐng)求ServletRequest、ServletResponse 強(qiáng)轉(zhuǎn)為HttpRequest 和 HttpResponse。
1、Servlet執(zhí)行過程
用戶請(qǐng)求一個(gè)Servlet,Servlet容器自動(dòng)構(gòu)建請(qǐng)求和響應(yīng)對(duì)象,然后執(zhí)行Servlet的service()方法,該方法會(huì)接收請(qǐng)求和響應(yīng)對(duì)象,通過響應(yīng)對(duì)象將處理結(jié)果發(fā)送給用戶。
2、怎么請(qǐng)求Servlet
用戶通過一個(gè)URL來請(qǐng)求一個(gè)Servlet。
3、怎么處理請(qǐng)求
當(dāng)用戶請(qǐng)求Servlet時(shí),容器構(gòu)建ServletRequest對(duì)象request,并傳遞給Servlet的Service()方法,Service通過request對(duì)象獲取到請(qǐng)求的參數(shù),然后,根據(jù)參數(shù)做出相應(yīng)的處理,通過ServletResponse對(duì)象來向客戶端發(fā)送響應(yīng)內(nèi)容。
4、怎么響應(yīng)客戶端
當(dāng)用戶請(qǐng)求一個(gè)Servlet時(shí)候,容器會(huì)自動(dòng)創(chuàng)建ServletResponse對(duì)象response,然后通過response對(duì)象向客戶端發(fā)送響應(yīng)內(nèi)容。
5、Servlet的生命周期
a、Servlet的class經(jīng)過部署,并啟動(dòng)容器
b、(當(dāng)請(qǐng)求該Servlet時(shí)候)容器自動(dòng)創(chuàng)建Servlet對(duì)象xServlet,然后xServlet調(diào)用其init()方法。到此,Servlet初始化結(jié)束。
c、一旦客戶端請(qǐng)求該Servlet,xServlet自動(dòng)調(diào)用service()來處理請(qǐng)求。
d、一旦很長(zhǎng)時(shí)間都沒有請(qǐng)求該Servlet(或者說該Servlet超時(shí)),則容器會(huì)將xServlet從容器中清除掉。
servlet的工作機(jī)制如下:
①客戶端(瀏覽器)在地址欄輸入一個(gè)URL發(fā)起HTTP請(qǐng)求。
②服務(wù)器根據(jù)URL指定要執(zhí)行的Servlet。
③servlet運(yùn)行service方法,并給服務(wù)器作出相應(yīng)。
④服務(wù)器接收到了servlet的響應(yīng)數(shù)據(jù),將數(shù)據(jù)返回給請(qǐng)求者。
⑤客戶端接受響應(yīng)數(shù)據(jù),作出展示。
應(yīng)該是tomcat里創(chuàng)建響應(yīng)的socketServer線程類接收請(qǐng)求連接,然后在再創(chuàng)建或引用對(duì)應(yīng)的servlet實(shí)例來處理請(qǐng)求連接。servlet是單例的,只創(chuàng)建一次。所以最好不要使用serlvet中的實(shí)例字段。。
hashmap面試經(jīng)常會(huì)被問到底層的數(shù)據(jù)結(jié)構(gòu)是什么,以及jdk1.7和1.8兩個(gè)版本hashmap的區(qū)別
AQS核心思想是,如果被請(qǐng)求的共享資源空閑,則將當(dāng)前請(qǐng)求資源的線程設(shè)置為有效的工作線程,并且將共享資源設(shè)置為鎖定狀態(tài)。如果被請(qǐng)求的共享資源被占用,那么就需要一套線程阻塞等待以及被喚醒時(shí)鎖分配的機(jī)制,這個(gè)機(jī)制AQS是用CLH隊(duì)列鎖實(shí)現(xiàn)的,即將暫時(shí)獲取不到鎖的線程加入到隊(duì)列中。 AQS使用一個(gè)voliate int成員變量來表示同步狀態(tài),通過內(nèi)置的FIFO隊(duì)列來完成獲取資源線程的排隊(duì)工作。AQS使用CAS對(duì)該同步狀態(tài)進(jìn)行原子操作實(shí)現(xiàn)對(duì)其值的修改。
AQS定義了兩種資源獲取方式:獨(dú)占(只有一個(gè)線程能訪問執(zhí)行,又根據(jù)是否按隊(duì)列的順序分為公平鎖和非公平鎖,如ReentrantLock) 和共享(多個(gè)線程可同時(shí)訪問執(zhí)行,如Semaphore/CountDownLatch,Semaphore、CountDownLatCh、 CyclicBarrier )。ReentrantReadWriteLock 可以看成是組合式,允許多個(gè)線程同時(shí)對(duì)某一資源進(jìn)行讀。
AQS底層使用了模板方法模式, 自定義同步器在實(shí)現(xiàn)時(shí)只需要實(shí)現(xiàn)共享資源 state 的獲取與釋放方式即可,至于具體線程等待隊(duì)列的維護(hù)(如獲取資源失敗入隊(duì)/喚醒出隊(duì)等),AQS已經(jīng)在上層已經(jīng)幫我們實(shí)現(xiàn)好了。
vue的底層原理面試題有,vue如何實(shí)現(xiàn)數(shù)據(jù)的響應(yīng)式?利用object.defineObject來實(shí)現(xiàn)的。
dom_diff的算法?
還有v_model的實(shí)現(xiàn)原理?以及生命周期是怎樣實(shí)現(xiàn)的?
還有nextTick的實(shí)現(xiàn)原理等等,這些都是vue的底層面試題
當(dāng)客戶端發(fā)出Web資源的請(qǐng)求時(shí),Web服務(wù)器根據(jù)應(yīng)用程序配置文件設(shè)置的過濾規(guī)則進(jìn)行檢查,客戶請(qǐng)求滿足過濾規(guī)則,則對(duì)客戶請(qǐng)求/響應(yīng)進(jìn)行攔截,對(duì)請(qǐng)求頭和請(qǐng)求數(shù)據(jù)進(jìn)行檢查或改動(dòng),并依次通過過濾器鏈,最后把請(qǐng)求/響應(yīng)交給請(qǐng)求的Web資源處理。
請(qǐng)求信息在過濾器鏈中可以被修改,也可以根據(jù)條件讓請(qǐng)求不發(fā)往資源處理器,并直接向客戶機(jī)發(fā)回一個(gè)響應(yīng)。
很多人可能和我當(dāng)初一樣,把Servlet和太多東西聯(lián)系起來。其實(shí)Servlet本身在Tomcat中是“非常被動(dòng)”的一個(gè)角色,處理的事情也很簡(jiǎn)單。網(wǎng)絡(luò)請(qǐng)求與響應(yīng),不是他的主要職責(zé),它其實(shí)更偏向于業(yè)務(wù)代碼。所謂的Request和Response是Tomcat傳給它,用來處理請(qǐng)求和響應(yīng)的工具,但它本身不處理這些。
文章會(huì)比較長(zhǎng),但是看完會(huì)拔高你看待Servlet的視角。
主要內(nèi)容:
類似于Servlet是Server Applet(運(yùn)行在服務(wù)端的小程序)等其他博文已經(jīng)提過的內(nèi)容,這里就不重復(fù)了。它就是用來處理請(qǐng)求的業(yè)務(wù)邏輯的。
之前在Tomcat外傳中我們聊過,所謂Tomcat其實(shí)是Web服務(wù)器和Servlet容器的結(jié)合體。
什么是Web服務(wù)器?
比如,我當(dāng)前在杭州,你能否用自己的電腦訪問我桌面上的一張圖片?恐怕不行。我們太習(xí)慣通過URL訪問一個(gè)網(wǎng)站、下載一部電影了。一個(gè)資源,如果沒有URL映射,那么外界幾乎很難訪問。而Web服務(wù)器的作用說穿了就是:將某個(gè)主機(jī)上的資源映射為一個(gè)URL供外界訪問。
什么是Servlet容器?
Servlet容器,顧名思義里面存放著Servlet對(duì)象。我們?yōu)槭裁茨芡ㄟ^Web服務(wù)器映射的URL訪問資源?肯定需要寫程序處理請(qǐng)求,主要3個(gè)過程:
任何一個(gè)應(yīng)用程序,必然包括這三個(gè)步驟。其中接收請(qǐng)求和響應(yīng)請(qǐng)求是共性功能,且沒有差異性。訪問淘寶和訪問京東,都是接收http://www.taobao.com/brandNo=1,響應(yīng)給瀏覽器的都是JSON數(shù)據(jù)。于是,大家就把接收和響應(yīng)兩個(gè)步驟抽取成Web服務(wù)器:
但處理請(qǐng)求的邏輯是不同的。沒關(guān)系,抽取出來做成Servlet,交給程序員自己編寫。
當(dāng)然,隨著后期互聯(lián)網(wǎng)發(fā)展,出現(xiàn)了三層架構(gòu),所以一些邏輯就從Servlet抽取出來,分擔(dān)到Service和Dao。
但是Servlet并不擅長(zhǎng)往瀏覽器輸出HTML頁(yè)面,所以出現(xiàn)了JSP。JSP的故事,我已經(jīng)在怎樣學(xué)習(xí)JSP?講過了。
等Spring家族出現(xiàn)后,Servlet開始退居幕后,取而代之的是方便的SpringMVC。SpringMVC的核心組件DispatcherServlet其實(shí)本質(zhì)就是一個(gè)Servlet。但它已經(jīng)自立門戶,在原來HttpServlet的基礎(chǔ)上,又封裝了一條邏輯。
總之,很多新手程序員框架用久了,甚至覺得SpringMVC就是SpringMVC,和Servlet沒半毛錢關(guān)系。
不知道從什么時(shí)候開始,我們已經(jīng)不再關(guān)心、甚至根本不知道到底誰調(diào)用了我寫的這個(gè)程序,反正我寫了一個(gè)類,甚至從來沒new過,它就跑起來了...
我們把模糊的記憶往前推一推,沒錯(cuò),就是在學(xué)了Tomcat后!從Tomcat開始,我們?cè)僖矝]寫過main方法。以前,一個(gè)main方法啟動(dòng),程序間的調(diào)用井然有序,我們知道程序所有流轉(zhuǎn)過程。
但是到了Javaweb后,Servlet/Filter/Listener一路下來我們?cè)綄W(xué)越沮喪。沒有main,也沒有new,寫一個(gè)類然后在web.xml中配個(gè)標(biāo)簽,它們就這么兀自運(yùn)行了。
其實(shí),這一切的一切,簡(jiǎn)單來說就是“注入”和“回調(diào)”。想象一下吧朋友們,Tomcat里有個(gè)main方法,假設(shè)是這樣的:
其實(shí),編程學(xué)習(xí)越往后越是如此,我們能做的其實(shí)很有限。大部分工作,框架都已經(jīng)幫我們做了。只要我們實(shí)現(xiàn)xxx接口,它會(huì)幫我們創(chuàng)建實(shí)例,然后搬運(yùn)(接口注入)到它合適的位置,然后一套既定的流程下來,肯定會(huì)執(zhí)行到。我只能用中國(guó)一個(gè)古老的成語(yǔ)形容這種開發(fā)模式:閉門造車,出門合轍(敲黑板,成語(yǔ)本身是夸技術(shù)牛逼,而不是說某人瞎幾把搞)。
很多時(shí)候,框架就像一個(gè)傀儡師,我們寫的程序是傀儡,頂多就是給傀儡化化妝、打扮打扮,實(shí)際的運(yùn)作全是傀儡師搞的。
了解到這個(gè)層面后,JavaWeb三大組件任何生命周期相關(guān)的方法、以及調(diào)用時(shí)Tomcat傳入的形參,這里就不再?gòu)?qiáng)調(diào)。肯定是程序的某處,在創(chuàng)建實(shí)例后緊接著就傳入?yún)?shù)調(diào)用了唄。沒啥神秘的。
首先,我們心里必須有一個(gè)信念:我們都是菜雞,框架肯定不會(huì)讓我們寫很難的代碼。
所以Servlet既然交給我們實(shí)現(xiàn),肯定是很簡(jiǎn)單的!
(沒有網(wǎng)絡(luò)請(qǐng)求和響應(yīng)需要我們處理,都封裝好了!)
你別不服。進(jìn)入Tomcat階段后,我們開始全面面向接口編程。但是“面向接口編程”這個(gè)概念,最早其實(shí)出現(xiàn)在JDBC階段。我就問你,JDBC接口是你自己實(shí)現(xiàn)的嗎?別鬧了,你導(dǎo)入MySQL的驅(qū)動(dòng)包,它給你搞定了一切。
真正的連接過程太難寫了,朋友們。底層就是TCP連接數(shù)據(jù)庫(kù)啊,你會(huì)嗎?寫Socket,然后進(jìn)行數(shù)據(jù)庫(kù)校驗(yàn),最后返回Connection?這顯然超出我們的能力范圍了。我們這么菜,JDBC不可能讓我們自己動(dòng)手的。所以各大數(shù)據(jù)庫(kù)廠商體貼地推出了驅(qū)動(dòng)包,里面有個(gè)Driver類,調(diào)用
driver.connect(url, username, password);
即可得到Connection。
BUT,這一次難得Tomcat竟然這么瞧得起我黃某 ,僅僅提供了javax.servlet接口,這是打算讓我自己去實(shí)現(xiàn)?
不,不可能的,肯定是因?yàn)樘?jiǎn)單了。
查看接口方法:
五個(gè)方法,最難的地方在于形參,然而Tomcat會(huì)事先把形參對(duì)象封裝好傳給我...除此以外,既不需要我寫TCP連接數(shù)據(jù)庫(kù),也不需要我解析HTTP請(qǐng)求,更不需要我把結(jié)果轉(zhuǎn)成HTTP響應(yīng),request對(duì)象和response對(duì)象幫我搞定了。
看吧,Tomcat是不是把我們當(dāng)成智障啊。
Tomcat之所以放心地交給我們實(shí)現(xiàn),是因?yàn)镾ervlet里主要寫的代碼都是業(yè)務(wù)邏輯代碼。和原始的、底層的解析、連接等沒有絲毫關(guān)系。最難的幾個(gè)操作,人家已經(jīng)給你封裝成形參傳進(jìn)來了。
也就是說,Servlet雖然是個(gè)接口,但實(shí)現(xiàn)類只是個(gè)空殼,我們寫點(diǎn)業(yè)務(wù)邏輯就好了。
總的來說,Tomcat已經(jīng)替我們完成了所有“菜雞程序員搞不定的騷操作”,并且傳入三個(gè)對(duì)象:ServletConfig、ServletRequest、ServletResponse。接下來,我們看看這三個(gè)傳進(jìn)來都是啥。
ServletConfig
翻譯過來就是“Servlet配置”。我們?cè)谀呐渲肧ervlet來著?web.xml嘛。請(qǐng)問你會(huì)用dom4j解析xml得到對(duì)象嗎?
可能...會(huì)吧,就是不熟練,嘿嘿嘿。
所以,Tomcat還真沒錯(cuò)怪我們,已經(jīng)幫“菜雞們”搞掂啦:
也就是說,servletConfig對(duì)象封裝了servlet的一些參數(shù)信息。如果需要,我們可以從它獲取。
Request/Response
兩位老朋友,不用多介紹了。也是題主最在意的網(wǎng)絡(luò)請(qǐng)求相關(guān)的內(nèi)容,其實(shí)是Tomcat處理的并封裝好了的,不需要Servlet操心。很多人看待HTTP和Request/Response的眼光過于分裂。它們的關(guān)系就像菜園里的大白菜和餐桌上的酸辣白菜一樣。HTTP請(qǐng)求到了Tomcat后,Tomcat通過字符串解析,把各個(gè)請(qǐng)求頭(Header),請(qǐng)求地址(URL),請(qǐng)求參數(shù)(QueryString)都封裝進(jìn)了Request對(duì)象中。通過調(diào)用
request.getHeader();
request.getUrl();
request.getQueryString();
...
等等方法,都可以得到瀏覽器當(dāng)初發(fā)送的請(qǐng)求信息。
至于Response,Tomcat傳給Servlet時(shí),它還是空的對(duì)象。Servlet邏輯處理后得到結(jié)果,最終通過response.write()方法,將結(jié)果寫入response內(nèi)部的緩沖區(qū)。Tomcat會(huì)在servlet處理結(jié)束后,拿到response,遍歷里面的信息,組裝成HTTP響應(yīng)發(fā)給客戶端。
Servlet接口5個(gè)方法,其中init、service、destroy是生命周期方法。init和destroy各自只執(zhí)行一次,即servlet創(chuàng)建和銷毀時(shí)。而service會(huì)在每次有新請(qǐng)求到來時(shí)被調(diào)用。也就是說,我們主要的業(yè)務(wù)代碼需要寫在service中。
但是,瀏覽器發(fā)送請(qǐng)求最基本的有兩種:Get/Post,于是我們必須這樣寫:
很煩啊。有沒有辦法簡(jiǎn)化這個(gè)操作啊?我不想直接實(shí)現(xiàn)javax.servlet接口啊。
于是,菜雞程序員找了下,發(fā)現(xiàn)了GenericServlet,是個(gè)抽象類。
我們發(fā)現(xiàn)GenericServlet做了以下改良:
放棄GenericServlet。
于是我們繼續(xù)尋找,又發(fā)現(xiàn)了HttpServlet:
它繼承了GenericServlet。
GenericServlet本身是一個(gè)抽象類,有一個(gè)抽象方法service。查看源碼發(fā)現(xiàn),HttpServlet已經(jīng)實(shí)現(xiàn)了service方法:
好了,也就是說HttpServlet的service方法已經(jīng)替我們完成了復(fù)雜的請(qǐng)求方法判斷。
但是,我翻遍整個(gè)HttpServlet源碼,都沒有找出一個(gè)抽象方法。所以為什么HttpServlet還要聲明成抽象類呢?
看一下HttpServlet的文檔注釋:
一個(gè)類聲明成抽象方法,一般有兩個(gè)原因:
HttpServlet做成抽象類,僅僅是為了不讓new。
它為什么不希望被實(shí)例化,且要求子類重寫doGet、doPost等方法呢?
我們來看一下源碼:
如果我們沒重寫會(huì)怎樣?
瀏覽器頁(yè)面會(huì)顯示:405(http.method_get_not_supported)
也就是說,HttpServlet雖然在service中幫我們寫了請(qǐng)求方式的判斷。但是針對(duì)每一種請(qǐng)求,業(yè)務(wù)邏輯代碼是不同的,HttpServlet無法知曉子類想干嘛,所以就抽出七個(gè)方法,并且提供了默認(rèn)實(shí)現(xiàn):報(bào)405、400錯(cuò)誤,提示請(qǐng)求不支持。
但這種實(shí)現(xiàn)本身非常雞肋,簡(jiǎn)單來說就是等于沒有。所以,不能讓它被實(shí)例化,不然調(diào)用doXxx方法是無用功。
Filter用到了責(zé)任鏈模式,Listener用到了觀察者模式,Servlet也不會(huì)放過使用設(shè)計(jì)模式的機(jī)會(huì):模板方法模式。上面的就是。這個(gè)模式我在JDBC的博文里講過了:JDBC(中)
小結(jié):
想看看Servlet與SpringMVC關(guān)系的朋友請(qǐng)戳:
bravo1988:Servlet(下)最近新寫的小冊(cè),圖文并茂,通俗易懂:
bravo1988:中級(jí)Java程序員如何進(jìn)階1、大學(xué)期間,我學(xué)的是法學(xué)專業(yè),在四年的專業(yè)學(xué)習(xí)中研讀了法律,作為一個(gè)優(yōu)秀的法律人,是我的理想。
2、法院作為守護(hù)正義的最后一道防衛(wèi)線,法官作為公平正義的守護(hù)神,一直是我向往的職業(yè)。我希望能夠?qū)⑽业乃鶎W(xué)運(yùn)用到工作中,在法院工作中更好的學(xué)習(xí)。