- JSP網(wǎng)絡(luò)編程(學(xué)習(xí)筆記)
- 傅進(jìn)勇 鄧少烽 李波編著
- 11字
- 2019-01-01 07:17:34
第二篇 為“Servlet核心技術(shù)”
第3章 Servl et基礎(chǔ)
前兩章學(xué)習(xí)了Web的基礎(chǔ)知識(shí)、HTTP協(xié)議、HTML等知識(shí),見過(guò)Servlet和JSP的例子,也學(xué)習(xí)了一些工具的配置,已經(jīng)有足夠的基礎(chǔ)進(jìn)行下一步的學(xué)習(xí)了。本章將介紹Servlet的基礎(chǔ)知識(shí),
本章內(nèi)容包括
★ Servlet的基本結(jié)構(gòu)及其常用方法介紹。
★ 了解跟Servlet相關(guān)的對(duì)象。
★ 進(jìn)一步學(xué)習(xí)在Web容器中配置Servlet。
通過(guò)本章的學(xué)習(xí),讀者將明白Servlet的具體含義,知道Servlet的結(jié)構(gòu)及其各個(gè)方法的作用。除了第2章介紹的Servlet配置外,還將學(xué)習(xí)更多在Web容器中配置Servlet的知識(shí)。
進(jìn)入第03章
3.1 Servlet介紹
在前面的章節(jié)中,已經(jīng)了解過(guò)什么是Servlet,以及它跟Web容器是如何配合工作的。本節(jié)將對(duì)這些內(nèi)容作一個(gè)回顧并總結(jié)。
3.1.1 什么是Servlet
在第1章的介紹中可以知道,Servlet的意思就是“服務(wù)器端程序”,也就是說(shuō)它是運(yùn)行在Web容器內(nèi)的Java程序,在Web程序的調(diào)度下工作,用于處理用戶請(qǐng)求并生成動(dòng)態(tài)資源。
從語(yǔ)言結(jié)構(gòu)上來(lái)看,一個(gè)Servlet就是一個(gè)Java類,但要求繼承自特定的Servlet基類,如圖3-1所示。

圖3-1 Servlet的繼承結(jié)構(gòu)
從圖3-1中可以看出最上層是一個(gè)抽象類javax.servlet.GenericServlet。雖然就目前而言,Servlet只用于Web服務(wù),但它的目標(biāo)并不限于此,也希望在FTP、Email等其他服務(wù)中使用Servlet技術(shù),因此該類的定義規(guī)定了所有使用Servlet技術(shù)的服務(wù)都必須遵循的接口,不管是Web服務(wù)、FTP服務(wù)(如果以后使用的話),都必須遵循這里定義的接口。
中間一層是javax.servlet.http.HttpServlet,它實(shí)現(xiàn)了GenericServlet中定義的接口,同時(shí)也有自己的擴(kuò)展,因?yàn)閷?duì)于Web服務(wù)來(lái)說(shuō),它使用的是HTTP協(xié)議,有HTTP協(xié)議的特別之處。HttpServlet的實(shí)現(xiàn)一般由Web容器提供,在第2章介紹如何編譯Servlet時(shí),就可以看到如何將Tomcat提供的包加入到CLASSPATH中讓編譯器使用。
前面兩層介紹的是Servlet的底層結(jié)構(gòu),但不必對(duì)它過(guò)分深究,只需了解即可,這里關(guān)心的是上圖中最下面一層——自己編寫的Servlet。由前面介紹可以知道,Servlet實(shí)際只是普通的Java類,但對(duì)于使用HTTP協(xié)議的Servlet,自己編寫的類必須繼承HttpServlet,這樣Web容器才會(huì)承認(rèn)它。根據(jù)需要將重載其中某些方法;除此之外,Servlet并無(wú)任何特殊之處。
提示
在本書中,提到的Servlet是專指繼承自HttpServlet的子類。因?yàn)榫湍壳岸裕?span id="sud7mqn" class="italic">Servlet都是用于Web服務(wù)的,都派生自HttpServlet。很多文獻(xiàn)、資料也是這樣不加區(qū)分地使用這些詞語(yǔ)的。
3.1.2 Web容器如何找到Servlet
現(xiàn)在已經(jīng)揭開Servlet的神秘面紗,知道它只是個(gè)符合一定要求的Java類。接下來(lái)需要知道,Web容器是如何使用Servlet的。直接回答這個(gè)問(wèn)題較麻煩,先將它分成幾個(gè)小問(wèn)題,如下所示。
1 Servlet應(yīng)該放在哪些地方,使得Web容器可以找到并使用它?
2 瀏覽器對(duì)服務(wù)器內(nèi)部組織一無(wú)所知,發(fā)送過(guò)來(lái)的只是URL,服務(wù)器如何判斷是否需要Servlet來(lái)處理?如果需要又如何確定由哪個(gè)Servlet來(lái)處理?
3 我們知道可以在Web容器中同時(shí)部署多個(gè)Web程序,但是瀏覽器不知道這一點(diǎn),它只知道發(fā)送URL。那么Web容器又應(yīng)該如何確定這個(gè)請(qǐng)求該由哪個(gè)Web程序負(fù)責(zé)處理?
在實(shí)際中,Web容器是按從(3)到(1)的順序解決上述這些問(wèn)題的,其過(guò)程如圖3-2所示。

圖3-2 Web容器根據(jù)URL查找Servlet的過(guò)程
當(dāng)接收到來(lái)自瀏覽器的請(qǐng)求時(shí),Web容器首先確定該由哪個(gè)Web應(yīng)用程序來(lái)處理該請(qǐng)求。如圖3-2所示,這是根據(jù)URL中第一個(gè)目錄的名稱,通過(guò)查找Web容器的配置文件來(lái)決定的,圖中第一個(gè)目錄的名稱是“webappname”。
需要注意,這一步的配置與Web容器相關(guān),沒(méi)有統(tǒng)一標(biāo)準(zhǔn),在開發(fā)時(shí)應(yīng)參考所用Web容器的文檔。如Tomcat可以通過(guò)對(duì)Tomcat安裝目錄“/conf/”下的文件進(jìn)行修改來(lái)實(shí)現(xiàn)。所幸的是很多Web容器都提供了一種簡(jiǎn)單的默認(rèn)方法,如Tomcat,如果第一個(gè)目錄名為“webappname”,則它使用Tomcat安裝目錄“/webapps/webappname”這個(gè)目錄下的Web程序來(lái)進(jìn)行處理。目前使用默認(rèn)方法即可。
提示
這里還有不少細(xì)節(jié)問(wèn)題。如在Tomcat中,如果找不到對(duì)應(yīng)的Web程序時(shí)它會(huì)如何處理?這個(gè)問(wèn)題的回答是,Tomcat將使用默認(rèn)的程序——也就是放在webapps/ROOT/下面的這個(gè)程序來(lái)處理。如URL為http://localhost:8080/webappname/others/,但webapps目錄下卻沒(méi)有webappname這個(gè)Web程序,這時(shí)Tomcat將使用默認(rèn)的Web程序。注意,這時(shí)Web程序名為空字符串,而將“/webappname/others/ ”這部分提供給Web程序進(jìn)行下一步的處理。
Web容器確定處理的Web程序后,還需要根據(jù)URL中剩余的部分進(jìn)一步確定如何處理。其中一個(gè)依據(jù)就是web程序配置文件——也就是Web程序目錄下的/WEB-INF/web.xml文件,這在圖3-2中可以看到。對(duì)Servlet來(lái)說(shuō),一般需要在web.xml中設(shè)定URL跟Servlet的對(duì)應(yīng)關(guān)系,這樣當(dāng)接收到符合該定義的URL時(shí),Web容器將調(diào)用相應(yīng)的Servlet進(jìn)行處理。
提示
如果在web.xml中沒(méi)有對(duì)如何對(duì)應(yīng)當(dāng)前的URL進(jìn)行定義,Web容器將使用默認(rèn)的處理方式進(jìn)行。如URL為http://localhost:8080/webappname/dire1/try.jsp,其中“/dire1/try.jsp ”沒(méi)有在web.xml中指定對(duì)應(yīng)規(guī)則,則Web容器默認(rèn)使用 webappname 這個(gè)程序跟目錄下的 dire1/try.jsp 這個(gè)文件匹配,然后再根據(jù)文件的后綴名進(jìn)行處理。如假設(shè)后綴名是 htm、html、jpg等,Web容器自動(dòng)設(shè)置合適的HTTP響應(yīng)頭,并將文件內(nèi)容返回;如果后綴是 jsp 的,則Web容器執(zhí)行該JSP——JSP的內(nèi)容將在第三篇學(xué)習(xí)。這當(dāng)然是對(duì)支持JSP的容器來(lái)說(shuō)的,如果是只支持ASP的Web容器又有不同的默認(rèn)規(guī)則,它可能只將JSP文件作為普通的文件返回。
在實(shí)際開發(fā)中,對(duì)Servlet一般通過(guò)配置web.xml跟URL對(duì)應(yīng),而其他則一般使用默認(rèn)方式來(lái)進(jìn)行。
關(guān)于Servlet跟URL的對(duì)應(yīng),第2章介紹了Tomcat的一種簡(jiǎn)單的配置方法,讀者可以重溫一下第2章的內(nèi)容;在本章的第3.4節(jié)將繼續(xù)介紹標(biāo)準(zhǔn)的方法,它是Servlet的標(biāo)準(zhǔn),對(duì)所有Web容器都適用。
如果Web容器確定當(dāng)前的URL應(yīng)該由Servlet處理,根據(jù)Web程序的配置,它會(huì)調(diào)用相應(yīng)的Servlet。由前面內(nèi)容已經(jīng)知道,Servlet只是個(gè)Java類,因此在配置時(shí),Servlet需要指定它完整的類名——即包含包名的完整名稱,如mystudy.MyServlet。Web容器調(diào)用這個(gè)類的工作跟普通的Java程序一樣——如果還沒(méi)加載,Web容器將在類路徑(CLASSPATH)中尋找相應(yīng)的class文件并加載,之后再調(diào)用它。
Web容器在啟動(dòng)時(shí)自動(dòng)將Web程序根目錄/WEB-INF/classes/ 作為該Web程序的類路徑之一。常見的做法,也是好的習(xí)慣,是將所有的Servlet都放在classes目錄下,如圖3-2所示這樣Web容器就可以使用這里的Servlet。當(dāng)然,將Servlet放到其他地方甚至Web程序目錄之外也是可以的,只要所在的位置包含在類路徑中即可,但這樣帶來(lái)的混亂將是難以想象的。
3.1.3 Servlet生命周期
現(xiàn)在讀者應(yīng)該已經(jīng)明白Servlet是什么,也明白了Web容器如何根據(jù)請(qǐng)求尋找Servlet進(jìn)行處理了。但Servlet在Web容器內(nèi)是怎么樣運(yùn)轉(zhuǎn)的,或者說(shuō),Servlet的生命周期是怎樣的?本節(jié)將探討這個(gè)問(wèn)題。
如圖3-3所示,Servlet首先由Web容器加載,然后才能運(yùn)行。一般情況下,Web容器在第一次遇到需要由該Servlet處理的請(qǐng)求時(shí),才對(duì)它進(jìn)行加載;但也可以通過(guò)web.xml的配置在Web容器啟動(dòng)并裝載Web程序時(shí)自動(dòng)加載該Servlet,這些將在第3.4節(jié)中的配置中介紹。

圖3-3 Servlet的生命周期
Servlet生命周期主要集中在“等待”和“處理請(qǐng)求”這兩步。當(dāng)遇到需要該Servlet處理的請(qǐng)求時(shí),Web容器將自動(dòng)調(diào)用Servlet的特定方法進(jìn)行處理——這將在下一節(jié)介紹。
最后,當(dāng)Web容器認(rèn)為不需要該Servlet時(shí),就對(duì)它進(jìn)行卸載。那么該什么時(shí)候卸載呢?當(dāng)Web容器關(guān)閉時(shí),一切的Servlet都會(huì)被卸載;或者該Servlet已經(jīng)很長(zhǎng)時(shí)間沒(méi)有處理請(qǐng)求了(該時(shí)間一般可通
過(guò)Web容器相關(guān)的配置來(lái)指定),為了節(jié)省資源,Web容器也會(huì)將它卸載。
注意
卸載并不表示該Servlet從此就不能處理請(qǐng)求。如某Servlet因長(zhǎng)時(shí)間沒(méi)處理請(qǐng)求,被卸載了;但之后終于來(lái)了需要它處理的請(qǐng)求,這時(shí)Web容器又會(huì)重新加載它。
3.2 Servlet的基本結(jié)構(gòu)
現(xiàn)在已經(jīng)知道了Servlet在Web容器中運(yùn)作的生命周期。如果想進(jìn)一步了解如何控制生命周期各部分的行為,編寫出滿足自己需要的Servlet,就需要了解Servlet的結(jié)構(gòu),本節(jié)講述的就是這部分內(nèi)容。
Web應(yīng)用使用的是HttpServlet。簡(jiǎn)單來(lái)說(shuō),Web容器是通過(guò)在合適的時(shí)候調(diào)用HttpServlet類特定的方法來(lái)實(shí)現(xiàn)它們之間的合作關(guān)系,這些方法構(gòu)成了Servlet的基本結(jié)構(gòu)。Servlet派生自HttpServlet,根據(jù)需要重載特定方法并編寫自己的代碼,就可以控制Servlet的行為,以實(shí)現(xiàn)自己的需求。主要的方法如圖3-4所示。

圖3-4 Servlet的結(jié)構(gòu)
圖中列出的方法由Web容器在合適的時(shí)候調(diào)用,以下各節(jié)將更詳細(xì)解釋這些方法。
3.2.1 init方法
init方法由Web容器在加載時(shí)Servlet調(diào)用,可以在這里進(jìn)行初始化工作,以方便后面的處理。如某Servlet頻繁地處理請(qǐng)求,但在處理時(shí)需要使用數(shù)據(jù)庫(kù)或其他資源,如果覺(jué)得在每次處理時(shí)才分配這些資源效率太低,則可以重載該方法,在加載時(shí)首先獲取數(shù)據(jù)庫(kù)連接和其他資源并保存起來(lái),之后在處理請(qǐng)求時(shí)就可以直接使用這些資源了。
注意
有人可能認(rèn)為,對(duì)每個(gè)獨(dú)立的請(qǐng)求,Web容器都構(gòu)建一個(gè)新的Servlet對(duì)象來(lái)專門處理該請(qǐng)求,因此得出init方法會(huì)頻繁地被調(diào)用的結(jié)論。這是不對(duì)的,Web容器只會(huì)構(gòu)建唯一一個(gè)Servlet對(duì)象,但所有的處理都由它進(jìn)行,因此init方法在Servlet的生存期間“會(huì)且僅會(huì)”被調(diào)用一次。
在圖3-4中可以看到,init方法有兩個(gè)原型,分別如下:
1 void init(ServletConfig config)
2 void init()
這兩個(gè)方法有什么區(qū)別呢?如圖3-5所示。

圖3-5 加載時(shí)Web容器調(diào)用init方法
當(dāng)接收到需要Servlet進(jìn)行處理的請(qǐng)求時(shí),如果Web容器得知該Servlet還沒(méi)加載(或者已經(jīng)卸載了),則Web容器會(huì)先加載該Servlet,然后才能將請(qǐng)求交給它處理。
Servlet加載后,Web容器會(huì)調(diào)用方法(1),讓Servlet進(jìn)行初始化工作。這里有兩點(diǎn)需要注意。
首先是傳入的參數(shù),這是個(gè)ServletConfig對(duì)象。通過(guò)Web程序描述文件web.xml配置S ervlet時(shí),可以給它設(shè)定一些“名:值”形式的參數(shù)。Web容器在調(diào)用方法(1)時(shí),會(huì)將這些參數(shù)信息封裝在ServletConfig對(duì)象中,這樣Servlet在初始化或后續(xù)處理時(shí)就可以參考這些參數(shù)信息了。關(guān)于ServletConfig對(duì)象的使用以及在web.xml中配置參數(shù)的方法,本章后面幾節(jié)會(huì)有更詳細(xì)的說(shuō)明。
其次,HttpServlet所實(shí)現(xiàn)的public void init(ServletConfig config) 中做了兩項(xiàng)工作。
★ 保存ServletConfig對(duì)象。
在圖3-4中可以看到,ServletConfig對(duì)象只在方法(1)被調(diào)用時(shí)傳入,其他方法沒(méi)有。如果希望其他方法也可以訪問(wèn)這個(gè)對(duì)象,則必須將它保存起來(lái)。方法(1)在HttpServlet中的默認(rèn)實(shí)現(xiàn)做了這一步,在其他方法中,可以通過(guò)調(diào)用public ServletConfig getServletConfig() 這個(gè)方法來(lái)獲得,如:

★ 調(diào)用不帶參數(shù)的方法init()。
方法(1)的默認(rèn)實(shí)現(xiàn)會(huì)調(diào)用方法(2),也就是不帶參數(shù)的init()。上面剛講到,默認(rèn)的方法(1)會(huì)保存ServletConfig,這里希望使用這個(gè)功能,因此不應(yīng)該重載方法(1);但如果確實(shí)有一些初始化工作需要在init方法中完成,該如何做?這時(shí)可以重載方法(2)。默認(rèn)的方法(2)是不做任何工作的,因此開發(fā)中一般重載方法(2)而保留方法(1),代碼如下:

3.2.2 service方法
當(dāng)需要Servlet處理請(qǐng)求時(shí),Web容器會(huì)調(diào)用它的service方法,該方法的原型是:
void service(HttpServletRequest req, HttpServletResponse resp)
運(yùn)作的示意圖如圖3-6所示。

圖3-6 Web容器調(diào)用service方法處理請(qǐng)求
從圖3-6可以看到,當(dāng)Web容器需要Servlet處理的請(qǐng)求時(shí),它會(huì)調(diào)用service(…)方法。
Web容器會(huì)傳給service方法兩個(gè)參數(shù),分別是HttpServletRequest、HttpServletResponse的對(duì)象。這兩個(gè)對(duì)象是由Web容器創(chuàng)建,且是獨(dú)立于請(qǐng)求的,也就是說(shuō),對(duì)每個(gè)請(qǐng)求Web容器都會(huì)創(chuàng)建兩個(gè)新對(duì)象——這跟使用同一個(gè)Servlet對(duì)象處理所有的請(qǐng)求是不同的。
HttpServletRequest對(duì)象封裝了本次請(qǐng)求的相關(guān)信息,程序調(diào)用它的方法就可以方便地獲得這些信息。例如,該對(duì)象封裝了HTTP請(qǐng)求,只需要調(diào)用方法就可以獲得HTTP消息頭的內(nèi)容,也調(diào)用方法通過(guò)名字獲取“名=值”形式的參數(shù)值——不管是跟在URL的或放在消息體的都可以,而無(wú)需自己寫代碼解釋。
HttpServletResponse則封裝了響應(yīng)內(nèi)容。例如,程序只需調(diào)用它的方法指明返回哪些消息頭,調(diào)用方法返回資源內(nèi)容即可,該對(duì)象自動(dòng)將這些數(shù)據(jù)組織成HTTP響應(yīng)的格式返回。
關(guān)于HttpServletRequest、HttpServletResponse的使用,后面幾節(jié)會(huì)簡(jiǎn)單介紹,更深入的內(nèi)容則在后續(xù)章節(jié)討論。
看到這里,讀者可能會(huì)想:“那么,只需重載service方法,添加代碼就能定制自己的Servlet了。”是的,這種想法是正確的。在下一節(jié)還會(huì)繼續(xù)深入討論這個(gè)問(wèn)題。
3.2.3 doGet、doPost、doXxx方法
從名字,讀者可能會(huì)猜想它們應(yīng)該跟HTTP的請(qǐng)求方法有聯(lián)系。是的,它們跟service方法一樣,都是為處理請(qǐng)求而定義的。分出這幾個(gè)方法的目的是方便Servlet可以對(duì)不同的HTTP請(qǐng)求方法做不同的處理。
這些方法的原型都如下:
(void doXXX(HttpServletRequest req, HttpServletResponse resp)
XXX是符號(hào),HttpServlet的定義中,每種HTTP請(qǐng)求都有對(duì)應(yīng)的方法,如最常用的doGet,doPost;此外還有doPut,doDelete,doTrace等。可以看出,除了方法名它們跟service方法的原型是一樣的。而這些方法的調(diào)用時(shí)機(jī)如圖3-7所示。

圖3-7 doXXX方法的調(diào)用時(shí)機(jī)
從圖3-7中可以看出,doXXX(…)和service(…)的關(guān)系類似于init()和init(…)。Web容器通過(guò)調(diào)用service方法來(lái)通知該Servlet處理請(qǐng)求,而默認(rèn)的實(shí)現(xiàn)(也就是HttpServlet中的實(shí)現(xiàn))是根據(jù)HTTP的請(qǐng)求方法來(lái)調(diào)用對(duì)應(yīng)的doXXX。當(dāng)請(qǐng)求方法為GET時(shí),service調(diào)用doGet;當(dāng)請(qǐng)求方法為POST時(shí),調(diào)用doPOST。同樣如果請(qǐng)求方法為PUT,DELETE等,則分別調(diào)用doPut,doDelete。不過(guò)在實(shí)際中,大部分的應(yīng)用都只用GET和POST,很少使用其他方法。
現(xiàn)在繼續(xù)討論上一節(jié)最后提出的問(wèn)題。現(xiàn)在知道,可以重載service方法來(lái)處理請(qǐng)求,也可以重載doXXX方法而保留默認(rèn)的service來(lái)處理請(qǐng)求,應(yīng)該如何選擇?
根據(jù)上面的討論知道,Web容器是直接調(diào)用service方法的,如果重載了它,則對(duì)于跟它相關(guān)的所有請(qǐng)求,不管請(qǐng)求方法是什么都會(huì)做相同的處理;但如果保留service的默認(rèn)機(jī)制,則會(huì)根據(jù)請(qǐng)求方法調(diào)用對(duì)應(yīng)的doXXX,顯得較靈活。
提示
事實(shí)上,由于各種各樣的原因,處理不同的HTTP請(qǐng)求時(shí)總會(huì)遇到不少細(xì)節(jié)問(wèn)題。如請(qǐng)求時(shí)以“名=值”形式附帶的參數(shù),處理它們時(shí)經(jīng)常會(huì)遇到編碼問(wèn)題。因?yàn)樗鼈兛梢愿綆г谡?qǐng)求消息頭的URL后,也可以用POST方法在消息體中發(fā)送。
因此,為了擴(kuò)展的方便,建議一般采用后一種處理方式——保留默認(rèn)的service方法,重載需要的doXXX。即使確定對(duì)所有請(qǐng)求方法其處理方式都一樣,但為了將來(lái)不可預(yù)測(cè)的擴(kuò)展問(wèn)題,也盡量不要覆蓋service,而采用類似下面的方式。public class MyServlet extends HttpServlet public void doGet(HttpServletRequest req, HttpServletResponse resp) ……{……}public void doPost(HttpServletRequest req, HttpServletResponse resp) ……{doGet(req, resp);}}此處是處理請(qǐng)求的代碼使用和doGet相同的處理方式
3.2.4 destroy方法
現(xiàn)在來(lái)談destroy方法,它和init方法是相對(duì)的。Web容器加載Servlet后會(huì)調(diào)用init方法,進(jìn)行數(shù)據(jù)、資源的初始化等準(zhǔn)備工作;而Web容器卸載Servlet時(shí),會(huì)調(diào)用destroy方法,提供了讓Servlet進(jìn)行資源釋放、數(shù)據(jù)保存的機(jī)會(huì)。它的原型如下:
void destroy()
如圖3-8所示列出了卸載Servlet的過(guò)程。

圖3-8 卸載Servlet的過(guò)程
關(guān)于該過(guò)程,需要注意以下幾點(diǎn)。
★ 什么時(shí)候需要卸載Servlet?當(dāng)Web容器關(guān)閉時(shí),所有的Servlet自然會(huì)卸載,但很多Web容器都會(huì)監(jiān)測(cè)Servlet的使用情況,如果某個(gè)Servlet長(zhǎng)時(shí)間沒(méi)處理請(qǐng)求(也就是一直處于生命周期中的“等待”狀態(tài)),Web容器也會(huì)卸載它。
★ Web容器在卸載前,必須讓Servlet不再處理新的請(qǐng)求。要注意的是,這并非意味著需要該Servlet處理的請(qǐng)求將得不到響應(yīng)。必須明白,Web容器內(nèi)每個(gè)Servlet都只有一個(gè)實(shí)例對(duì)象在處理請(qǐng)求。對(duì)每個(gè)請(qǐng)求,Web容器會(huì)啟動(dòng)一個(gè)新的線程,該線程執(zhí)行同一個(gè)Servlet實(shí)例的代碼,使用同一個(gè)Servlet的成員數(shù)據(jù),而不是對(duì)每個(gè)請(qǐng)求都新建一個(gè)對(duì)象——讀者可能記得在介紹init方法時(shí)已提過(guò)這一點(diǎn)了。因此所謂的“不再處理新的請(qǐng)求”,是指當(dāng)前的Servlet實(shí)例不再處理請(qǐng)求;但Web容器可以在完成卸載工作后,重新創(chuàng)建新的實(shí)例并讓它處理請(qǐng)求。當(dāng)然,新創(chuàng)建的Servlet實(shí)例有自己獨(dú)立的生命周期,跟前面的實(shí)例沒(méi)有太大的聯(lián)系。
★ Web容器還會(huì)盡量讓Servlet完成正在處理的請(qǐng)求,但它只會(huì)等待一段時(shí)間,超時(shí)了處理還未完成則會(huì)強(qiáng)行終止。之后Web容器調(diào)用Servlet的destroy方法進(jìn)行清理工作。
3.2.5 Servlet結(jié)構(gòu)綜合
前面已經(jīng)學(xué)習(xí)了Servlet結(jié)構(gòu),知道了主要的方法及其調(diào)用機(jī)制,本節(jié)將給出一個(gè)例子來(lái)將這些內(nèi)容綜合起來(lái)。由于目前還未學(xué)習(xí)Servlet相關(guān)的對(duì)象和數(shù)據(jù)庫(kù)操作等知識(shí),因此例子中這方面的操作都用偽代碼來(lái)代替源代碼,但通過(guò)這個(gè)例子可以從整體上了解Servlet編寫的結(jié)構(gòu),而后續(xù)學(xué)習(xí)的則是對(duì)該結(jié)構(gòu)下各部分內(nèi)容的細(xì)化。
這個(gè)例子中的Servlet除了處理請(qǐng)求外,還帶有統(tǒng)計(jì)訪問(wèn)次數(shù)的功能,設(shè)計(jì)如下:
★ 訪問(wèn)次數(shù)存放在數(shù)據(jù)庫(kù)的特定表中,加載時(shí)Servlet從數(shù)據(jù)庫(kù)中獲取該數(shù)值作為初始值并保存在成員變量中,之后處理每個(gè)請(qǐng)求時(shí)都將該變量加一。在卸載時(shí)將該數(shù)值保存到數(shù)據(jù)庫(kù)。
★ 所用的表名及字段名是固定的,而所用的數(shù)據(jù)庫(kù)、登錄數(shù)據(jù)庫(kù)的名字、密碼等內(nèi)容則在web. xml中通過(guò)參數(shù)形式配置,可以通過(guò)所傳入的ServletConfig對(duì)象獲得這些信息。
該偽代碼如圖3-9所示。

圖3-9 Servlet示例
這個(gè)例子雖然簡(jiǎn)單,但它綜合了本節(jié)所學(xué)的內(nèi)容。要注意實(shí)際中還需要考慮更多的問(wèn)題,如下:
★ Servlet一般是同時(shí)處理多個(gè)請(qǐng)求的,而Web容器為每個(gè)請(qǐng)求專門建立一個(gè)線程來(lái)處理,因此上面的代碼可能出現(xiàn)多個(gè)線程同時(shí)增加訪問(wèn)計(jì)數(shù)的值——記住,只有一個(gè)Servlet對(duì)象在處理請(qǐng)求,從而只有一個(gè)計(jì)數(shù)值供所有線程共同使用。因此還需要考慮多線程對(duì)數(shù)據(jù)修改的同步問(wèn)題。
★ 實(shí)際中還會(huì)遇到不可預(yù)測(cè)的問(wèn)題導(dǎo)致Servlet不能正常卸載。如系統(tǒng)突然死機(jī),這時(shí)Servlet將計(jì)數(shù)值保存到數(shù)據(jù)庫(kù)的操作將不能正確進(jìn)行。為此,可能要修改設(shè)計(jì),如取消將計(jì)數(shù)值保存在成員變量的做法,而直接修改數(shù)據(jù)庫(kù)中的數(shù)據(jù),但如果每次都操作數(shù)據(jù)庫(kù)太影響效率,這時(shí)又要考慮如何平衡了。
當(dāng)然這些問(wèn)題的全面考慮超出了Servlet能控制的范圍了。跟許多知識(shí)類似,學(xué)習(xí)Servlet并不太難,但設(shè)計(jì)嚴(yán)格的程序卻并不簡(jiǎn)單。
3.3 Servlet相關(guān)的主要對(duì)象
上一節(jié)介紹的Servlet主要方法中,有的需要Web容器傳入?yún)?shù)。如init方法中傳入的ServletConfig對(duì)象,service、doXXX方法中傳入的HttpServletRequest、HttpServletResponse對(duì)象,本節(jié)簡(jiǎn)單介紹這幾個(gè)對(duì)象,它們的作用如圖3-10所示。

圖3-10 Servlet相關(guān)的主要對(duì)象
ServletConfig是定義在javax.servlet中的一個(gè)接口(interface),包含了Servlet相關(guān)的信息,由Web容器在調(diào)用init時(shí)傳入。
ServletConfig定義的方法及作用如圖3-11所示。

圖3-11 ServletConfig的方法
HttpServletRequest是定義在javax.servlet.http中的接口,它是獲取請(qǐng)求信息的核心對(duì)象,定義了很多方法,也提供了很強(qiáng)大的功能。但這里只簡(jiǎn)單介紹幾個(gè)方法,如圖3-12所示。后續(xù)章節(jié)還有關(guān)于這些方法的詳細(xì)說(shuō)明,有需要時(shí)也可以查閱Servlet的API文檔。

圖3-12 HttpServletRequest的幾個(gè)方法
最后來(lái)看HttpServletResponse,它是定義在javax.servlet.http中的一個(gè)接口。它封裝了HTTP返回響應(yīng)信息的操作,程序只需調(diào)用適當(dāng)?shù)姆椒ㄔO(shè)置消息頭或傳入要返回的內(nèi)容即可,Web容器會(huì)將這些數(shù)據(jù)按HTTP的格式組織好再返回到瀏覽器。
該對(duì)象是進(jìn)行響應(yīng)操作的核心,也定義了很多方便而且功能強(qiáng)大的方法。現(xiàn)在只介紹其中幾個(gè),如圖3-13所示。后續(xù)章節(jié)會(huì)對(duì)它定義的方法作更詳細(xì)的說(shuō)明。

圖3-13 HttpServletResponse的幾個(gè)方法
使用HttpServletResponse的方法,最需要注意的是順序問(wèn)題,因?yàn)镠TTP格式要求的順序是先消息頭后消息體,而消息頭的響應(yīng)狀態(tài)碼又必須在第一行,因此調(diào)用時(shí)最好按先后順序。讀者可能會(huì)認(rèn)為Web容器是收集完所有響應(yīng)信息和內(nèi)容后,再一起組織成HTTP格式返回,但實(shí)際上一般不是這樣的,因?yàn)槿绻麑?duì)多個(gè)同時(shí)的請(qǐng)求都這樣處理,內(nèi)存占用量是很可觀的;限制緩沖大小雖然在一定程序上緩解這個(gè)問(wèn)題,但假如發(fā)送了大量資源內(nèi)容的數(shù)據(jù)(數(shù)據(jù)放在消息體中,數(shù)量超過(guò)緩沖大小,使得已有數(shù)據(jù)被返回到瀏覽器),再返回來(lái)設(shè)置消息頭,這時(shí)消息頭就會(huì)被忽略——這是Servlet規(guī)范明確定義的。
一般返回HTML頁(yè)面的Servlet可以用如下的代碼模板。

3.4 Servlet的基本配置
在第2章學(xué)習(xí)了如何在Tomcat中配置Servlet的一種方法,如果忘記了可以回顧一下。那種方法很方便,只需設(shè)置一次即對(duì)所有Servlet生效,如果沒(méi)特殊原因,一般用它來(lái)進(jìn)行調(diào)試學(xué)習(xí)。
但該方法帶來(lái)的問(wèn)題也是明顯的,可以總結(jié)如下:
★ Servlet是跟Web容器相關(guān)的,雖然Tomcat可以這樣用,但其他Web容器未必就可以這樣,這是最大的問(wèn)題。
★ 它一旦設(shè)置就對(duì)所有Servlet都生效,但有時(shí)只希望部分Servlet能直接被瀏覽器訪問(wèn),用這種方式則不夠靈活。這里的“直接訪問(wèn)”是指通過(guò)URL來(lái)直接得到Servlet處理,實(shí)際上Servlet標(biāo)準(zhǔn)還規(guī)定,可以在處理過(guò)程中跳轉(zhuǎn)到其他的Servlet,這些在后續(xù)章節(jié)中會(huì)學(xué)習(xí)。
事實(shí)上,Servlet規(guī)范專門為Servlet的配置定義了一種標(biāo)準(zhǔn)方法,本節(jié)將進(jìn)行介紹。它是在Web應(yīng)用程序描述文件(也就是WEB-INF/web.xml)進(jìn)行配置的,在任何支持Servlet的Web容器中都能使用。
由于要修改web.xml,因此在正式講解前,先簡(jiǎn)單介紹一下web.xml的結(jié)構(gòu)要求。如圖3-14所示,它列出了web.xml文件的大致結(jié)構(gòu)。

圖3-14 web.xml的標(biāo)簽順序
web.xml是按照一定要求編寫的XML文件。實(shí)際應(yīng)用中很少會(huì)從頭寫起,一般是復(fù)制一份模板進(jìn)行修改,在接下來(lái)的相關(guān)章節(jié)中,讀者可以使用Tomcat安裝目錄下的/webapps/ROOT/WEB-INF/web.xml文件作為模板。
關(guān)于web.xml各標(biāo)簽元素的說(shuō)明,后續(xù)章節(jié)會(huì)逐步介紹。現(xiàn)在需要注意以下幾點(diǎn)。
★ 如圖3-14所示,<!DOCTYPE…>標(biāo)簽中有兩個(gè)數(shù)字表示web.xml文件的版本。因?yàn)殡S著Servlet及JSP技術(shù)的發(fā)展,可能需要越來(lái)越多的標(biāo)簽以提供更多的配置項(xiàng),也可能為了方便而改變格式。Web容器會(huì)根據(jù)指定的版本號(hào)來(lái)檢查內(nèi)容是否符合要求,如果使用了新版本的寫法,但卻提供舊的版本號(hào),Web容器將不能正確識(shí)別。圖3-14列出的是版本2.3的大致結(jié)構(gòu)。
★ web.xml的所有配置項(xiàng)都包含在<web-app>…</web-app>這對(duì)標(biāo)簽下。
★ 所有標(biāo)簽都是可選的,也就是說(shuō)可以根據(jù)自己的需要而提供部分配置的標(biāo)簽。各標(biāo)簽都有自己的格式要求,如可以配置哪些屬性、可以包含哪些子標(biāo)簽等。圖3-14中只是列出了示意圖,并沒(méi)有詳細(xì)列出各標(biāo)簽的屬性、可包含子標(biāo)簽等內(nèi)容。
★ 在2.3或之前的版本中,標(biāo)簽必須嚴(yán)格按照?qǐng)D3-14列出的順序排列。例如,在web.xml中提供了<description>和<servlet>這兩個(gè)標(biāo)簽,則<servlet>…</servlet>必須跟在<description>…</description>后面,否則Web容器檢查格式時(shí)會(huì)報(bào)錯(cuò)。讀者在web.xml增加標(biāo)簽時(shí),可參考該圖的順序。
本節(jié)中介紹的Servlet配置跟<servlet>和<servlet-mapping>這兩個(gè)標(biāo)簽有關(guān)。<servlet>標(biāo)簽定義了Servlet的“名字”與“類名”的對(duì)應(yīng)關(guān)系,而<servlet-mapping>則提供了URL跟“名字”的對(duì)應(yīng)關(guān)系。也就是說(shuō),URL和Servlet是通過(guò)“名字”這個(gè)“中間人”來(lái)聯(lián)系的,其示意關(guān)系如圖3-15所示。

圖3-15 URL和Servlet class通過(guò)“名字”聯(lián)系
3.4.1 Servlet的名字及路徑配置
這一節(jié)介紹<servlet>標(biāo)簽如何提供“名字”并將它跟實(shí)際的class聯(lián)系。在web.xml中可以通過(guò)如下方式實(shí)現(xiàn)。

<servlet-name>定義了Servlet名字。其他和Servlet相關(guān)的標(biāo)簽都是通過(guò)這個(gè)“名字”來(lái)關(guān)聯(lián)的,這樣在改變Servlet的類名時(shí)也不影響這些標(biāo)簽。Servlet的init方法中會(huì)傳入一個(gè)ServletConfig對(duì)象,調(diào)用它的getServletName()方法,返回的就是這個(gè)名字。
<servlet-class>則指定該名字關(guān)聯(lián)的完整類名——包含package、class的完整名稱。這和Java源文件中“import”指定完整類名的方式是一樣的,Web容器(實(shí)際上是運(yùn)行Web容器的Java虛擬機(jī))會(huì)在類路徑(CLASSPATH)中尋找它的class文件,Web程序下的WEB-INF/classes目錄正是路徑之一,如果忘記了可回顧第3.1.2節(jié)——Web容器如何找到Servlet。
3.4.2 初始化參數(shù)
Servlet被初始化時(shí),Web容器調(diào)用init方法并傳入一個(gè)ServletConfig對(duì)象作為參數(shù),通過(guò)調(diào)用該對(duì)象的getInitParameter(String name)方法,可以獲取為該Servlet設(shè)置的參數(shù)。這些參數(shù)在web.xml中的設(shè)置方法示例如下:

如例中所示,參數(shù)是在<servlet>標(biāo)簽內(nèi),用<init-param>標(biāo)簽進(jìn)行定義。<init-param>必須跟在<servlet-class>后。每個(gè)<init-param>標(biāo)簽定義一個(gè)參數(shù),用<param-name>定義名字,用<param-value>定義參數(shù)值;多個(gè)參數(shù)則需要多個(gè)<init-param>標(biāo)簽。
示例定義了兩個(gè)參數(shù),如用“名=值”的方式寫出分別是“dbUserName=admin”、“dbPassword=secret”。在該Servlet中則可以獲取該參數(shù),如下:

3.4.3 啟動(dòng)裝入優(yōu)先級(jí)
Servlet一般是第一次遇到請(qǐng)求時(shí)加載并初始化的。但如果初始化要做的工作很多,如Servlet需要使用大量在運(yùn)行過(guò)程中不變的常數(shù),而這些常數(shù)零散地存儲(chǔ)在多個(gè)數(shù)據(jù)庫(kù)中,在初始化時(shí)就需要從多個(gè)數(shù)據(jù)庫(kù)分別獲取這些數(shù)據(jù)。如果這個(gè)過(guò)程耗費(fèi)的時(shí)間較多,如十幾秒,這樣第一個(gè)請(qǐng)求該Servlet的用戶就要等待相當(dāng)長(zhǎng)的時(shí)間才能得到響應(yīng),這就顯得不夠友好了。
所以希望這樣的Servlet能在第一次請(qǐng)求前就已經(jīng)進(jìn)行了初始化。Servlet標(biāo)準(zhǔn)考慮到這點(diǎn)需求并定義了相應(yīng)地規(guī)則,只要在<servlet>標(biāo)簽里提供了子標(biāo)簽<load-on-startup>,Web程序啟動(dòng)時(shí)就會(huì)同時(shí)加載并初始化這個(gè)Servlet。配置的例子如下:

<load-on-startup>標(biāo)簽中還可以包含一個(gè)數(shù)字,表示啟動(dòng)時(shí)初始化的順序。數(shù)字越小則越早進(jìn)行初始化,而沒(méi)有指定的則總是在最后,如下:

現(xiàn)在寫幾個(gè)Servlet來(lái)驗(yàn)證以上討論的內(nèi)容。該Servlet很簡(jiǎn)單,只是在初始化時(shí)往標(biāo)準(zhǔn)輸出流輸出一個(gè)字符串。在Tomcat中,標(biāo)準(zhǔn)輸出流的數(shù)據(jù)顯示在console窗口中,可以直接看到結(jié)果。而其他的Web容器可能會(huì)重定向到特定的文件,如果讀者使用的不是Tomcat,應(yīng)注意參考文檔看看數(shù)據(jù)被送到哪里去。
這里定義的三個(gè)類分別是FirstServlet、SecondServlet、LastServlet,代碼及web.xml的配置如圖3-16所示。

圖3-16 測(cè)試啟動(dòng)時(shí)Servlet初始化順序的代碼和配置
編譯Java文件,將class文件放在Web程序目錄/WEB-INF/classes/com/cxpub/ 下;按圖中配置文件修改web.xml,注意標(biāo)簽是有順序要求的,讀者可參考圖3-14所示列出的順序進(jìn)行修改。
啟動(dòng)(或重啟)Tomcat,窗口會(huì)顯示啟動(dòng)時(shí)輸出的信息,如圖3-17所示。

圖3-17 啟動(dòng)時(shí)Servlet初始化的輸出信息
如圖3-17所示,信息顯示的先后為“1st… 2nd… last…”,讀者對(duì)照?qǐng)D3-16中的配置,可以發(fā)現(xiàn)<load-on-startup>中數(shù)字小的確實(shí)先加載,而沒(méi)指定數(shù)字的則排到最后。圖3-17中最下面是Tomcat顯示啟動(dòng)完成需要的時(shí)間,說(shuō)明這些Servlet確實(shí)是在加載Web程序時(shí)(Tomcat啟動(dòng)時(shí)自動(dòng)加載所有Web程序)進(jìn)行初始化的。
3.4.4 URL到Servlet的映射
到現(xiàn)在為止,相信讀者已經(jīng)基本掌握了Web容器和Servlet的協(xié)作關(guān)系了,知道Web容器在什么時(shí)候調(diào)用Servlet的什么方法,知道怎樣通過(guò)配置使Servlet初始化時(shí)獲得參數(shù)……但還缺少最關(guān)鍵的一步,就是怎樣將URL跟處理請(qǐng)求的Servlet對(duì)應(yīng)起來(lái)。本節(jié)專門討論這個(gè)問(wèn)題,解決后,從瀏覽器發(fā)送URL、Web容器查找Servlet、Servlet處理并返回響應(yīng)這一系列的步驟,讀者都可以有個(gè)完整的認(rèn)識(shí)了。
URL到Servlet的映射,是指定義一個(gè)“URL模式”和指定一個(gè)Servlet,使得當(dāng)瀏覽器請(qǐng)求的URL符合這個(gè)模式時(shí),Web容器會(huì)調(diào)用指定的Servlet來(lái)處理請(qǐng)求。這個(gè)映射關(guān)系在web.xml中用<servlet-mapping>標(biāo)簽來(lái)定義的,運(yùn)作過(guò)程如圖3-18所示。

圖3-18 URL和Servlet映射的運(yùn)作示例
如圖3-18所示,<servlet-mapping>必須在<servlet>標(biāo)簽后給出。<servlet>里面按順序包含<servlet-name>和<url-pattern>這兩個(gè)標(biāo)簽。
<servlet-name>指定所關(guān)聯(lián)的Servlet的名字,而該名字必須在之前提供的<servlet>標(biāo)簽中出現(xiàn)。
<url-pattern>這個(gè)標(biāo)簽給出需要該Servlet處理時(shí),URL必須匹配的模式。這里需要注意如下內(nèi)容。
★ URL模式匹配的是相對(duì)于Web程序目錄的URL,而不是瀏覽器發(fā)送過(guò)來(lái)的整個(gè)URL。如圖3-18所示,Web容器會(huì)“抽取”第一個(gè)目錄名用于決定處理該請(qǐng)求的Web程序,剩余的部分(第一個(gè)目錄名后,但不包括參數(shù))才由被選中的Web程序進(jìn)行URL匹配。圖中,剩余部分是 “/servlet”,剛好和<servlet-mapping>指定的模式完全匹配。要注意的是,匹配過(guò)程對(duì)大小寫敏感。
★ 本例中<url-pattern>給出的是完整的URL,但實(shí)際上還有很多靈活的寫法。Servlet標(biāo)準(zhǔn)定義了如下的匹配方式。
1 “/目錄/*”方式,以斜杠(/)開頭,斜杠加通配符(/*)結(jié)尾。在這種模式中,以“/目錄”作為開頭的URL的都可以匹配,注意地址不需要包含通配符前的斜杠。如對(duì)于 /servlet/*,/servlet、 /servlet/Example都能匹配。
2 “*.ext”方式,以通配符加點(diǎn)號(hào)(*.)開頭,后綴名結(jié)尾,表示以.ext為結(jié)尾的地址都可以匹配。例如對(duì)于 *.jsp,則 /a.jsp、/dict/dict1/b.jsp都可以匹配。
3 “/”方式,僅包含一個(gè)斜杠(/),表示默認(rèn)的匹配方式。當(dāng)找不到其他匹配項(xiàng)時(shí),Web容器會(huì)提供自己的默認(rèn)處理方式,一般情況下不定義這種類型的URL模式。
4 其他方式,用于完全匹配。只有當(dāng)URL和模式完全一樣時(shí)才匹配。如對(duì)于/mydict/,/mydict/sub、/mydict都不匹配,因?yàn)樗鼈儾煌耆嗤?/p>
★ 如果web.xml提供了多個(gè)<servlet-mapping>,則URL使用最接近的匹配。Servlet規(guī)范定義了匹配的順序,一旦匹配成功則停止,其匹配順序如下:
1 按完全匹配的方式比較。
2 按<servlet-mapping>的定義順序查找前綴最長(zhǎng)的匹配。例如定義了兩個(gè)模式為 /dict/longer/*、 /dict/*,而URL為 /dict/longer/class.ext,則匹配前一個(gè)。
3 按后綴查找匹配。例如在(2)中如果沒(méi)有匹配,則看是否有定義 *.ext這樣的模式,有的話就和它匹配。
4 當(dāng)以上方式都無(wú)法匹配時(shí),將使用默認(rèn)的處理方式。Web容器一般將根據(jù)Web程序根目錄/對(duì)應(yīng)URL的文件 來(lái)決定如何處理。如請(qǐng)求的URL為http://localhost:8080/chpt3/nowhere/none.html時(shí),“/nowhere/none.html”找不到匹配項(xiàng),則Web容器嘗試查找“Web程序根目錄/nowhere/none.html”這個(gè)文件并作后續(xù)處理。但如果定義了默認(rèn)的匹配項(xiàng)——僅有斜杠的方式,就不會(huì)使用Web容器的默認(rèn)方式,因此使用時(shí)要注意。
嘗試
第2章中介紹過(guò)Tomcat有一種方便訪問(wèn)Servlet的方法,請(qǐng)讀者嘗試?yán)斫膺@種的配置的大概過(guò)程,它是如何通過(guò) /servlet/class-full-name 這種方式就可以訪問(wèn)所有Servlet的?提示:它是由Tomcat自帶的Servlet來(lái)處理并調(diào)用Web程序里面的Servlet。Tomcat安裝目錄下的conf/web. xml文件的內(nèi)容會(huì)和所有Web程序的web.xml合并,而 common/classes/、common/lib/下的所有.jar 文件也加入到所有Web程序的類路徑中。
3.5 開發(fā)部署一個(gè)簡(jiǎn)單的Servlet
通過(guò)前面幾節(jié)的學(xué)習(xí),現(xiàn)在讀者應(yīng)該已經(jīng)掌握了Servlet的基本知識(shí),簡(jiǎn)單總結(jié)如下:
★ Web應(yīng)用的Servlet是個(gè)繼承javax.servlet.http.HttpServlet的Java類,需放在Web程序的類路徑中,一般放在Web程序根目錄/WEB-INF/classes/ 下。
★ Web容器會(huì)在初始化、通知處理請(qǐng)求、卸載時(shí)調(diào)用Servlet的init,service,destroy方法,而這些方法默認(rèn)的實(shí)現(xiàn)又會(huì)調(diào)用其他特定的方法。
★ 可以通過(guò)web.xml配置給Servlet指定“Servlet名”、設(shè)置參數(shù),這些信息可通過(guò)初始化時(shí)Web容器傳入的ServletConfig對(duì)象獲得。
★ Servlet是通過(guò)HttpServletRequest對(duì)象獲取請(qǐng)求信息,通過(guò)HttpServletResponse對(duì)象返回響應(yīng)信息。這由Web容器在調(diào)用service方法時(shí)傳入,而service調(diào)用其他方法時(shí)也傳入這些對(duì)象。
★ 可在web.xml文件中配置URL和Servlet的映射關(guān)系,瀏覽器請(qǐng)求發(fā)送的URL符合指定的模式則由對(duì)應(yīng)的Servlet處理。
本節(jié)通過(guò)一個(gè)簡(jiǎn)單的servlet實(shí)例來(lái)綜合這些知識(shí),簡(jiǎn)單設(shè)計(jì)如下:
★ 完整類名是com.cxpub.chpt3.ExamServlet;
★ 在web.xml文件對(duì)這個(gè)Servlet配置,名為“my example servlet”,同時(shí)提供兩個(gè)初始參數(shù),其“名:值”對(duì)方式是“param1:1st”、“param2:2nd”;
★ 可以處理GET、POST方式的請(qǐng)求,處理時(shí)會(huì)獲取瀏覽器發(fā)送的參數(shù),參數(shù)名為“input”;
★ Servlet返回一個(gè)HTML文檔,文檔中顯示它配置的Servlet名、兩個(gè)初始參數(shù),還有瀏覽器發(fā)送的參數(shù)input。
Servlet的代碼如圖3-19所示。

圖3-19 ExamServlet的代碼
編譯這個(gè)文件,可以直接使用命令行方式調(diào)用javac編譯,但要先將Tomcat的Servlet包加到CLASSPATH中,調(diào)用方法如下:

將編譯好的ExamServlet.class文件放到“程序目錄/WEB-INF/classes/com/cxpub/chpt3/”下(注意源文件中的package com.cxpub.chpt3,放置的目錄要跟它一致)。
打開WEB-INF/web.xml文件,在合適的位置加入如圖3-20所示的配置代碼。可以在Tomcat自帶的Web程序中復(fù)制一份過(guò)來(lái)修改,只需按圖3-14要求的順序?qū)⑦@些內(nèi)容插入到適當(dāng)?shù)奈恢眉纯伞?/p>

圖3-20 web.xml的配置片段
在配置中將URL模式設(shè)置為“/ExamServlet”,假如Web程序放在mystudy目錄下,則可通過(guò)http://localhost:8080/mystudy/ExamServlet來(lái)訪問(wèn)該Servlet。
要做的工作就這么多,現(xiàn)在來(lái)看結(jié)果。假如Web程序放在為mystudy目錄下,則應(yīng)在瀏覽器中輸入如下URL。

圖3-21 測(cè)試?yán)拥娘@示結(jié)果
現(xiàn)在在附加參數(shù)中輸入HTML的特殊字符,如“input=<b>good</b>”,這時(shí)的情況如圖3-22所示。

圖3-22 輸入帶HTML保留字符參數(shù)的結(jié)果
可以看到,“<b>good</b>”這個(gè)參數(shù)沒(méi)有正確顯示。看一下瀏覽器得到的HTML文檔就知道原因了,如圖3-23所示。

圖3-23 圖3-22顯示頁(yè)面的源文件
從圖3-23可以看到,雖然Servlet獲取和輸出參數(shù)都沒(méi)問(wèn)題,但輸出內(nèi)容包含的HTML標(biāo)簽被瀏覽器解釋了。為了保證正確性,應(yīng)該在輸出數(shù)據(jù)前,先將HTML的保留字符用其他表達(dá)方式替換,如下:
< -> < > -> > & -> & " -> " 空格->  ;
事實(shí)上,不少設(shè)計(jì)較差的網(wǎng)站經(jīng)常因?yàn)檫@種情況出現(xiàn)問(wèn)題,比如一些論壇,如果用戶的主題內(nèi)容包含像“<”這類字符,而網(wǎng)站輸出時(shí)沒(méi)有轉(zhuǎn)換,則顯示的內(nèi)容就可能會(huì)混亂。
注意
在參數(shù)中使用中文字符,會(huì)出現(xiàn)如圖3-24所示的亂碼。這次確實(shí)是Servlet獲取參數(shù)過(guò)程出現(xiàn)問(wèn)題了,和文字編碼有關(guān)。下一章將會(huì)詳細(xì)討論這個(gè)問(wèn)題。

圖3-24 中文參數(shù)引起亂碼
上面測(cè)試結(jié)果時(shí),都是用和web.xml定義的模式匹配的URL來(lái)訪問(wèn)的。但在Tomcat中可以通過(guò)設(shè)置,使用“/Servlet/完整類名”來(lái)訪問(wèn)servlet的方式,如果用這種方法來(lái)訪問(wèn)本例中的Servlet,在Tomcat 5.5里測(cè)試結(jié)果如圖3-25所示。

圖3-25 使用Tomcat的特殊方式訪問(wèn)Servlet
可以看到,web.xml中的配置完全沒(méi)有反映出來(lái)。從中可以明白,Tomcat這種特殊方法雖然方便,但卻并不能代替所有的Servlet應(yīng)用,畢竟它只是與Web容器相關(guān)的功能,并不是J2EE的標(biāo)準(zhǔn)。一般情況下,只能用它來(lái)方便進(jìn)行常用功能的調(diào)試,而對(duì)特殊功能,或者在實(shí)際Web應(yīng)用中,應(yīng)該按照J(rèn)2EE的標(biāo)準(zhǔn)方法,在web.xml中配置Servlet信息和定義URL模式,而瀏覽器則使用匹配模式的URL來(lái)訪問(wèn)Servlet。當(dāng)然,如果使用的是Tomcat,部署實(shí)際Web應(yīng)用時(shí)還要注意禁止使用特殊方式訪問(wèn)Servlet,避免讓Servlet留給外界一個(gè)“后門”。
提示
在Tomcat下用特殊方式訪問(wèn)Servlet時(shí),Web容器實(shí)際另外創(chuàng)建了一個(gè)Servlet實(shí)例,而不是使用之前已經(jīng)存在的對(duì)象,因此它使用了另外的ServletConfig對(duì)象,從而輸出的信息和在web.xml中配置的不同。這種做法違背了“Web容器只有一個(gè)Servlet實(shí)例處理請(qǐng)求的原則,畢竟它不是J2EE的標(biāo)準(zhǔn),因此實(shí)際部署時(shí)應(yīng)嚴(yán)格禁止這個(gè)配置。
本書后續(xù)與Servlet有關(guān)的內(nèi)容,如無(wú)特殊說(shuō)明均可使用用Tomcat提供的這種方便形式。在特殊情況下會(huì)有專門說(shuō)明,所以不必?fù)?dān)心后面學(xué)習(xí)時(shí)要一直循規(guī)蹈矩地做這些麻煩的web.xml文件配置。
3.6 小結(jié)
本章學(xué)習(xí)了Servlet的結(jié)構(gòu)和標(biāo)準(zhǔn)配置,現(xiàn)在簡(jiǎn)單總結(jié)一下。
Servlet的init方法在Servlet加載時(shí)被調(diào)用,該方法有public void init(ServletConfig config)和public void init()兩種形式,實(shí)際中一般只覆蓋不帶參數(shù)的形式,在其中進(jìn)行初始化工作,如分配資源、獲取后續(xù)處理需要的數(shù)據(jù)等。
Web容器是調(diào)用Servlet的Service方法來(lái)進(jìn)行處理的,默認(rèn)的做法是根據(jù)請(qǐng)求方法調(diào)用相應(yīng)的doXXX方法。一般不覆蓋service方法,而在doGet,doPost,doXXX中定制自己的功能。
destroy方法在卸載時(shí)被調(diào)用,應(yīng)該在這里做好一切善后工作,如清除和釋放init方法中占有的資源,保存需要的數(shù)據(jù)等。
Servlet的配置以及與URL的映射,是在Web程序描述文件web.xml中通過(guò)<servlet>和<servlet-mapping>這兩個(gè)標(biāo)簽實(shí)現(xiàn)的,這兩個(gè)標(biāo)簽通過(guò)“Servlet名”聯(lián)系起來(lái)。
<servlet>中可以按順序包含定義名字的<servlet-name>,指定完整類名的<servlet-class>,提供初始參數(shù)的<init-param>和設(shè)定啟動(dòng)時(shí)自動(dòng)加載的<load-on-startup>這4個(gè)標(biāo)簽。其中<init-param>可以出現(xiàn)多次,每個(gè)標(biāo)簽定義一個(gè)參數(shù),它順序包含<param-name>、<param-value>這兩個(gè)標(biāo)簽,分別定義參數(shù)名和參數(shù)值。
<servlet-mapping>中順序包含<servlet-name>、<url-pattern>兩個(gè)標(biāo)簽。<servlet-name>指定關(guān)聯(lián)的Servlet名字,它必須在之前的<servlet>中定義;<url-pattern>指定了相對(duì)Web程序起始路徑的URL模式,任何符合該模式的URL都將由指定的Servlet進(jìn)行處理。
- Learning PostgreSQL
- 數(shù)據(jù)結(jié)構(gòu)和算法基礎(chǔ)(Java語(yǔ)言實(shí)現(xiàn))
- Building a Game with Unity and Blender
- PHP程序設(shè)計(jì)(慕課版)
- PHP基礎(chǔ)案例教程
- C#程序設(shè)計(jì)教程
- Vue.js 3.0源碼解析(微課視頻版)
- Web Application Development with MEAN
- Hands-On Automation Testing with Java for Beginners
- 大數(shù)據(jù)分析與應(yīng)用實(shí)戰(zhàn):統(tǒng)計(jì)機(jī)器學(xué)習(xí)之?dāng)?shù)據(jù)導(dǎo)向編程
- C專家編程
- Django 3.0入門與實(shí)踐
- Spring Boot+MVC實(shí)戰(zhàn)指南
- 零基礎(chǔ)學(xué)HTML+CSS
- Vue.js 3應(yīng)用開發(fā)與核心源碼解析