Servlet 是什么?
Java Servlet 是運(yùn)行在 Web 服務(wù)器或應(yīng)用服務(wù)器上的程序,它是作為來自 Web 瀏覽器或其他 HTTP 客戶端的請求和 HTTP 服務(wù)器上的數(shù)據(jù)庫或應(yīng)用程序之間的中間層。
使用 Servlet,您可以收集來自網(wǎng)頁表單的用戶輸入,呈現(xiàn)來自數(shù)據(jù)庫或者其他源的記錄,還可以動(dòng)態(tài)創(chuàng)建網(wǎng)頁。
Java Servlet 通常情況下與使用 CGI(Common Gateway Interface,公共網(wǎng)關(guān)接口)實(shí)現(xiàn)的程序可以達(dá)到異曲同工的效果。
Servlet是sun公司提供的一門用于開發(fā)動(dòng)態(tài)web資源的技術(shù)。
Sun公司在其API中提供了一個(gè)servlet接口,用戶若想用發(fā)一個(gè)動(dòng)態(tài)web資源(即開發(fā)一個(gè)Java程序向?yàn)g覽器輸出數(shù)據(jù)),需要完成以下2個(gè)步驟:
1、編寫一個(gè)Java類,實(shí)現(xiàn)servlet接口。
2、把開發(fā)好的Java類部署到web服務(wù)器中。
按照一種約定俗成的稱呼習(xí)慣,通常我們也把實(shí)現(xiàn)了servlet接口的java程序,稱之為Servlet
Servlet 架構(gòu)
下圖顯示了 Servlet 在 Web 應(yīng)用程序中的位置。

Servlet 任務(wù)
Servlet 執(zhí)行以下主要任務(wù):
讀取客戶端(瀏覽器)發(fā)送的顯式的數(shù)據(jù)。這包括網(wǎng)頁上的 HTML 表單,或者也可以是來自 applet 或自定義的 HTTP 客戶端程序的表單。
讀取客戶端(瀏覽器)發(fā)送的隱式的 HTTP 請求數(shù)據(jù)。這包括 cookies、媒體類型和瀏覽器能理解的壓縮格式等等。
處理數(shù)據(jù)并生成結(jié)果。這個(gè)過程可能需要訪問數(shù)據(jù)庫,執(zhí)行 RMI 或 CORBA 調(diào)用,調(diào)用 Web 服務(wù),或者直接計(jì)算得出對應(yīng)的響應(yīng)。
發(fā)送顯式的數(shù)據(jù)(即文檔)到客戶端(瀏覽器)。該文檔的格式可以是多種多樣的,包括文本文件(HTML 或 XML)、二進(jìn)制文件(GIF 圖像)、Excel 等。
發(fā)送隱式的 HTTP 響應(yīng)到客戶端(瀏覽器)。這包括告訴瀏覽器或其他客戶端被返回的文檔類型(例如 HTML),設(shè)置 cookies 和緩存參數(shù),以及其他類似的任務(wù)。
Servlet 創(chuàng)建有三種方式
1、實(shí)現(xiàn) Servlet 接口
因?yàn)槭菍?shí)現(xiàn) Servlet 接口,所以我們需要實(shí)現(xiàn)接口里的方法。
下面我們也說明了 Servlet 的執(zhí)行過程,也就是 Servlet 的生命周期。

2、繼承 GenericServlet 類
它實(shí)現(xiàn)了 Servlet 接口除了 service 的方法,不過這種方法我們極少用。

3、繼承 HttpServlet 方法

創(chuàng)建 Servlet 的第三種方法,也是我們經(jīng)常用的方法。
這里只簡單講 Servlet 的三種創(chuàng)建方式,關(guān)于更詳細(xì)的應(yīng)用我們后面再說。
Servlet的運(yùn)行過程
Servlet程序是由WEB服務(wù)器調(diào)用,web服務(wù)器收到客戶端的Servlet訪問請求后:
①Web服務(wù)器首先檢查是否已經(jīng)裝載并創(chuàng)建了該Servlet的實(shí)例對象。如果是,則直接執(zhí)行第④步,否則,執(zhí)行第②步。
②裝載并創(chuàng)建該Servlet的一個(gè)實(shí)例對象。
③調(diào)用Servlet實(shí)例對象的init()方法。
④創(chuàng)建一個(gè)用于封裝HTTP請求消息的HttpServletRequest對象和一個(gè)代表HTTP響應(yīng)消息的HttpServletResponse對象,然后調(diào)用Servlet的service()方法并將請求和響應(yīng)對象作為參數(shù)傳遞進(jìn)去。
⑤WEB應(yīng)用程序被停止或重新啟動(dòng)之前,Servlet引擎將卸載Servlet,并在卸載之前調(diào)用Servlet的destroy()方法。
Servlet調(diào)用圖

Servlet接口實(shí)現(xiàn)類
Servlet接口SUN公司定義了兩個(gè)默認(rèn)實(shí)現(xiàn)類,分別為:GenericServlet、HttpServlet。
HttpServlet指能夠處理HTTP請求的servlet,它在原有Servlet接口上添加了一些與HTTP協(xié)議處理方法,它比Servlet接口的功能更為強(qiáng)大。因此開發(fā)人員在編寫Servlet時(shí),通常應(yīng)繼承這個(gè)類,而避免直接去實(shí)現(xiàn)Servlet接口。
HttpServlet在實(shí)現(xiàn)Servlet接口時(shí),覆寫了service方法,該方法體內(nèi)的代碼會自動(dòng)判斷用戶的請求方式,如為GET請求,則調(diào)用HttpServlet的doGet方法,如為Post請求,則調(diào)用doPost方法。因此,開發(fā)人員在編寫Servlet時(shí),通常只需要覆寫doGet或doPost方法,而不要去覆寫service方法。
通過Eclipse創(chuàng)建和編寫Servlet
選中com.hhl.servlet包,右鍵→New→Servlet,如下圖所示:

這樣,我們就通過Eclipse幫我們創(chuàng)建好一個(gè)名字為HelloServlet的Servlet,創(chuàng)建好的HelloServlet里面會有如下代碼:


這些代碼都是Eclipse自動(dòng)生成的,而web.xml文件中也多了<servlet></servlet>和<servlet-mapping></servlet-mapping>兩對標(biāo)簽,這兩對標(biāo)簽是配置HelloServlet的,如下所示:

然后我們就可以通過瀏覽器訪問HelloServlet這個(gè)Servlet,如下圖所示:

Servlet開發(fā)注意細(xì)節(jié)
Servlet訪問URL映射配置
由于客戶端是通過URL地址訪問web服務(wù)器中的資源,所以Servlet程序若想被外界訪問,必須把servlet程序映射到一個(gè)URL地址上,這個(gè)工作在web.xml文件中使用元素和元素完成。
元素用于注冊Servlet,它包含有兩個(gè)主要的子元素:和,分別用于設(shè)置Servlet的注冊名稱和Servlet的完整類名。
一個(gè)元素用于映射一個(gè)已注冊的Servlet的一個(gè)對外訪問路徑,它包含有兩個(gè)子元素:和,分別用于指定Servlet的注冊名稱和Servlet的對外訪問路徑。例如:

同一個(gè)Servlet可以被映射到多個(gè)URL上,即多個(gè)<servlet-mapping>元素的<servlet-name>子元素的設(shè)置值可以是同一個(gè)Servlet的注冊名。 例如:

通過上面的配置,當(dāng)我們想訪問名稱是HelloServlet的Servlet,可以使用如下的幾個(gè)地址去訪問:
???? ? http://localhost:8088/ServletDemo/
?????? http://localhost:8088/ServletDemo/1.html
HelloServlet被映射到了多個(gè)URL上。
Servlet訪問URL使用*通配符映射
在Servlet映射到的URL中也可以使用*通配符,但是只能有兩種固定的格式:一種格式是"*.擴(kuò)展名",另一種格式是以正斜杠(/)開頭并以"/*"結(jié)尾。例如:

*可以匹配任意的字符,所以此時(shí)可以用任意的URL去訪問ServletDemo1這個(gè)Servlet
對于如下的一些映射關(guān)系:
Servlet1 映射到 /abc/*
Servlet2 映射到 /*
Servlet3 映射到 /abc
Servlet4 映射到 *.do
問題:
當(dāng)請求URL為“/abc/a.html”,“/abc/*”和“/*”都匹配,哪個(gè)servlet響應(yīng)
Servlet引擎將調(diào)用Servlet1。
當(dāng)請求URL為“/abc”時(shí),“/abc/*”和“/abc”都匹配,哪個(gè)servlet響應(yīng)
Servlet引擎將調(diào)用Servlet3。
當(dāng)請求URL為“/abc/a.do”時(shí),“/abc/*”和“*.do”都匹配,哪個(gè)servlet響應(yīng)
Servlet引擎將調(diào)用Servlet1。
當(dāng)請求URL為“/a.do”時(shí),“/*”和“*.do”都匹配,哪個(gè)servlet響應(yīng)
Servlet引擎將調(diào)用Servlet2。
當(dāng)請求URL為“/xxx/yyy/a.do”時(shí),“/*”和“*.do”都匹配,哪個(gè)servlet響應(yīng)
Servlet引擎將調(diào)用Servlet2。
匹配的原則就是"誰長得更像就找誰"
Servlet與普通Java類的區(qū)別
Servlet是一個(gè)供其他Java程序(Servlet引擎)調(diào)用的Java類,它不能獨(dú)立運(yùn)行,它的運(yùn)行完全由Servlet引擎來控制和調(diào)度。
針對客戶端的多次Servlet請求,通常情況下,服務(wù)器只會創(chuàng)建一個(gè)Servlet實(shí)例對象,也就是說Servlet實(shí)例對象一旦創(chuàng)建,它就會駐留在內(nèi)存中,為后續(xù)的其它請求服務(wù),直至web容器退出,servlet實(shí)例對象才會銷毀。(注)
在Servlet的整個(gè)生命周期內(nèi),Servlet的init方法只被調(diào)用一次。而對一個(gè)Servlet的每次訪問請求都導(dǎo)致Servlet引擎調(diào)用一次servlet的service方法。對于每次訪問請求,Servlet引擎都會創(chuàng)建一個(gè)新的HttpServletRequest請求對象和一個(gè)新的HttpServletResponse響應(yīng)對象,然后將這兩個(gè)對象作為參數(shù)傳遞給它調(diào)用的Servlet的service()方法,service方法再根據(jù)請求方式分別調(diào)用doXXX方法。
如果在元素中配置了一個(gè)元素,那么WEB應(yīng)用程序在啟動(dòng)時(shí),就會裝載并創(chuàng)建Servlet的實(shí)例對象、以及調(diào)用Servlet實(shí)例對象的init()方法。
舉例:

當(dāng)訪問不存在的Servlet時(shí),就使用配置的默認(rèn)Servlet進(jìn)行處理
在<tomcat的安裝目錄>\conf\web.xml文件中,注冊了一個(gè)名稱為org.apache.catalina.servlets.DefaultServlet的Servlet,并將這個(gè)Servlet設(shè)置為了缺省Servlet。

當(dāng)訪問Tomcat服務(wù)器中的某個(gè)靜態(tài)HTML文件和圖片時(shí),實(shí)際上是在訪問這個(gè)缺省Servlet。
Servlet的線程安全問題
當(dāng)多個(gè)客戶端并發(fā)訪問同一個(gè)Servlet時(shí),web服務(wù)器會為每一個(gè)客戶端的訪問請求創(chuàng)建一個(gè)線程,并在這個(gè)線程上調(diào)用Servlet的service方法,因此service方法內(nèi)如果訪問了同一個(gè)資源的話,就有可能引發(fā)線程安全問題。例如下面的代碼:
不存在線程安全問題的代碼:

存在線程安全問題的代碼:

把i定義成全局變量,當(dāng)多個(gè)線程并發(fā)訪問變量i時(shí),就會存在線程安全問題了,如下圖所示:同時(shí)開啟兩個(gè)瀏覽器模擬并發(fā)訪問同一個(gè)Servlet,本來正常來說,第一個(gè)瀏覽器應(yīng)該看到2,而第二個(gè)瀏覽器應(yīng)該看到3的,結(jié)果兩個(gè)瀏覽器都看到了3,這就不正常。
線程安全問題只存在多個(gè)線程并發(fā)操作同一個(gè)資源的情況下,所以在編寫Servlet的時(shí)候,如果并發(fā)訪問某一個(gè)資源(變量,集合等),就會存在線程安全問題,那么該如何解決這個(gè)問題呢?
先看看下面的代碼:

現(xiàn)在這種做法是給Servlet對象加了一把鎖,保證任何時(shí)候都只有一個(gè)線程在訪問該Servlet對象里面的資源,這樣就不存在線程安全問題了。
這種做法雖然解決了線程安全問題,但是編寫Servlet卻萬萬不能用這種方式處理線程安全問題,假如有9999個(gè)人同時(shí)訪問這個(gè)Servlet,那么這9999個(gè)人必須按先后順序排隊(duì)輪流訪問。
針對Servlet的線程安全問題,Sun公司是提供有解決方案的:讓Servlet去實(shí)現(xiàn)一個(gè)SingleThreadModel接口,如果某個(gè)Servlet實(shí)現(xiàn)了SingleThreadModel接口,那么Servlet引擎將以單線程模式來調(diào)用其service方法。
查看Sevlet的API可以看到,SingleThreadModel接口中沒有定義任何方法和常量,在Java中,把沒有定義任何方法和常量的接口稱之為標(biāo)記接口,經(jīng)??吹降囊粋€(gè)最典型的標(biāo)記接口就是"Serializable",這個(gè)接口也是沒有定義任何方法和常量的,標(biāo)記接口在Java中有什么用呢?主要作用就是給某個(gè)對象打上一個(gè)標(biāo)志,告訴JVM,這個(gè)對象可以做什么,比如實(shí)現(xiàn)了"Serializable"接口的類的對象就可以被序列化,還有一個(gè)"Cloneable"接口,這個(gè)也是一個(gè)標(biāo)記接口,在默認(rèn)情況下,Java中的對象是不允許被克隆的,就像現(xiàn)實(shí)生活中的人一樣,不允許克隆,但是只要實(shí)現(xiàn)了"Cloneable"接口,那么對象就可以被克隆了。
讓的Servlet實(shí)現(xiàn)了SingleThreadModel的接口,只要在Servlet的類的定義中增加實(shí)現(xiàn)了SingleThreadModel接口的聲明即可。 ?
對于實(shí)現(xiàn)了SingleThreadModel的接口的Servlet中,Servlet的引擎仍然支持對該Servlet的的多線程并發(fā)訪問,其采用的方式是產(chǎn)生多個(gè)Servlet的實(shí)例對象,并發(fā)的每個(gè)線程分別調(diào)用一個(gè)獨(dú)立的Servlet的實(shí)例對象。
實(shí)現(xiàn)了SingleThreadModel接口并不能真正解決的Servlet的線程安全問題,因?yàn)镾ervlet的引擎會創(chuàng)建多個(gè)Servlet的實(shí)例對象,而真正意義上解決多線程安全問題是指一個(gè)Servlet實(shí)例對象被多線程同時(shí)調(diào)用的問題。事實(shí)上,在Servlet API 2.4中,已經(jīng)將SingleThreadModel標(biāo)記為Deprecated(過時(shí)的)。?
那么防止線程安全的問題就是避免使用實(shí)例變量,采用局部變量的形式。如果應(yīng)用程序設(shè)計(jì)無法避免使用實(shí)例變量,那么使用同步來保護(hù)要使用的實(shí)例變量,但為保證系統(tǒng)的最佳性能,應(yīng)該同步可用性最小的代碼路徑。
Servlet的確已經(jīng)能夠幫我們完成所有的工作了,但是現(xiàn)在的web應(yīng)用很少有直接將交互全部頁面都用servlet來實(shí)現(xiàn),而是采用更高效的MVC框架來實(shí)現(xiàn)。這些MVC框架基本的原理都是將所有的請求都映射到一個(gè)Servlet,然后去實(shí)現(xiàn)service方法,這個(gè)方法也就是MVC框架的入口。如下圖是strust MVC的,為什么是performLogin,秘密就在PubAction.java中。


轉(zhuǎn)載:http://blog.csdn.net/honghailiang888。