— 了解如何通過實現(xiàn)Servlet接口來編寫Servlet — 掌握ServletRequest和ServletResponse接口 — 掌握ServletConfig接口 — 掌握GenericServlet和HttpServlet抽象類 — 掌握HttpServletRequest和HttpServletResponse接口 — 掌握Servlet開發(fā)中一些方法和技巧的使用 — 熟悉Servlet異常 — 掌握Servlet上下文 — 掌握RequestDispatcher對象的使用 — 區(qū)分sendRedirect()和forward()方法的使用 從這一章開始,,我 們將詳細介紹Java服務(wù)器端編程的重要技術(shù)——Servlet。 2.1 Servlet API這一節(jié)我們主要介紹一下開發(fā)Servlet需要用到的主要接口和類,,這些接口和類的UML類圖如圖2-1所示,。 2.1.1 Servlet接口在Java語言中,我們已經(jīng)了解了Java Applet(Java小應(yīng)用程序),。它運行在客戶端的瀏覽器中,。Java Applet與Java Servlet有以下一些共同點。 — 它們都不是獨立的應(yīng)用程序,,都沒有main()方法,。 — 它們都不是由用戶或程序員直接調(diào)用,而是生存在容器中,,由容器管理,。Applet運行在瀏覽器中,Servlet運行在Servlet容器中,。 — 它們都有生命周期,,都包含了init()和destroy()方法。 當然,,Applet與Servlet也有不同點,。 — Applet具有圖形界面,運行在客戶端的瀏覽器中,。 — Servlet沒有圖形界面,,運行在服務(wù)器端的Servlet容器中。 要編寫一個Applet,,需要從java.applet.Applet類派生一個子類,;和Applet類似,要編寫一個Servlet,,需要實現(xiàn)javax.servlet.Servlet接口,,該接口定義了如下5個方法。 Ø public void init(ServletConfig config) throws ServletException Ø public void service(ServletRequest req, ServletResponse res) throws ServletException, java.io.IOException Ø public void destroy() Ø public ServletConfig getServletConfig() Ø public java.lang.String getServletInfo() 下面介紹一下這5個方法的作用,。 — init():在Servlet實例化之后,,Servlet容器會調(diào)用init()方法,來初始化該對象,,主要是為了讓Servlet對象在處理客戶請求前可以完成一些初始化的工作,,例如,建立數(shù)據(jù)庫的連接,獲取配置信息等,。對于每一個Servlet實例,,init()方法只能被調(diào)用一次。init()方法有一個類型為ServletConfig的參數(shù),,Servlet容器通過這個參數(shù)向Servlet傳遞配置信息,。Servlet使用ServletConfig對象從Web應(yīng)用程序的配置信息中獲取以名-值對形式提供的初始化參數(shù)。另外,,在Servlet中,,還可以通過ServletConfig對象獲取描述Servlet運行環(huán)境的ServletContext對象,使用該對象,,Servlet可以和它的Servlet容器進行通信,。 — service():容器調(diào)用service()方法來處理客戶端的請求。要注意的是,,在service()方法被容器調(diào)用之前,,必須確保init()方法正確完成。容器會構(gòu)造一個表示客戶端請求信息的請求對象(類型為ServletRequest)和一個用于對客戶端進行響應(yīng)的響應(yīng)對象(類型為ServletResponse)作為參數(shù)傳遞給service()方法,。在service()方法中,,Servlet對象通過ServletRequest對象得到客戶端的相關(guān)信息和請求信息,在對請求進行處理后,,調(diào)用ServletResponse對象的方法設(shè)置響應(yīng)信息,。 — destroy():當容器檢測到一個Servlet對象應(yīng)該從服務(wù)中被移除的時候,,容器會調(diào)用該對象的destroy()方法,,以便讓Servlet對象可以釋放它所使用的資源,保存數(shù)據(jù)到持久存儲設(shè)備中,,例如,,將內(nèi)存中的數(shù)據(jù)保存到數(shù)據(jù)庫中,關(guān)閉數(shù)據(jù)庫的連接等,。當需要釋放內(nèi)存或者容器關(guān)閉時,,容器就會調(diào)用Servlet對象的destroy()方法。在Servlet容器調(diào)用destroy()方法前,,如果還有其他的線程正在service()方法中執(zhí)行,,容器會等待這些線程執(zhí)行完畢或等待服務(wù)器設(shè)定的超時值到達。一旦Servlet對象的destroy()方法被調(diào)用,,容器不會再把其他的請求發(fā)送給該對象。如果需要該Servlet再次為客戶端服務(wù),,容器將會重新產(chǎn)生一個Servlet對象來處理客戶端的請求,。在destroy()方法調(diào)用之后,容器會釋放這個Servlet對象,在隨后的時間內(nèi),,該對象會被Java的垃圾收集器所回收。 — getServletConfig():該方法返回容器調(diào)用init()方法時傳遞給Servlet對象的ServletConfig對象,,ServletConfig對象包含了Servlet的初始化參數(shù),。 — getServletInfo():返回一個String類型的字符串,其中包括了關(guān)于Servlet的信息,,例如,,作者、版本和版權(quán),。該方法返回的應(yīng)該是純文本字符串,,而不是任何類型的標記(HTML,、XML等),。 Servlet API包含在J2EE中,如果要查看Servlet API的文檔,,你需要下載J2EE(從J2EE 1.5開始改名為Java EE 5)SDK,Java EE 5 SDK的下載地址如下:http://java./javaee/downloads/?intcmp=1282,。 2.1.2 ServletRequest和ServletResponseServlet由Servlet容器來管理,,當客戶請求到來時,,容器創(chuàng)建一個ServletRequest對象,封裝請求數(shù)據(jù),同時創(chuàng)建一個ServletResponse對象,封裝響應(yīng)數(shù)據(jù),。這兩個對象將被容器作為service()方法的參數(shù)傳遞給Servlet,,Servlet利用ServletRequest對象獲取客戶端發(fā)來的請求數(shù)據(jù),,利用ServletResponse對象發(fā)送響應(yīng)數(shù)據(jù)。 ServletRequest和ServletResponse接口都在javax.servlet包中定義,,我們首先看一下ServletRequest接口中的常用方法,。 Ø public java.lang.Object getAttribute(java.lang.String name)返回以name為名字的屬性的值。如果該屬性不存在,,這個方法將返回null。 Ø public java.util.Enumeration getAttributeNames()返回請求中所有可用的屬性的名字,。如果在請求中沒有屬性,,這個方法將返回一個空的枚舉集合。 Ø public void removeAttribute(java.lang.String name)移除請求中名字為name的屬性,。 Ø public void setAttribute(java.lang.String name, java.lang.Object o)在請求中保存名字為name的屬性,。如果第二個參數(shù)o為null,那么相當于調(diào)用removeAttribute(name),。 Ø public java.lang.String getCharacterEncoding()返回請求正文使用的字符編碼的名字,。如果請求沒有指定字符編碼,這個方法將返回null。 Ø public int getContentLength()以字節(jié)為單位,,返回請求正文的長度,。如果長度不可知,這個方法將返回-1,。 Ø public java.lang.String getContentType()返回請求正文的MIME類型,。如果類型不可知,這個方法將返回null,。 Ø public ServletInputStream getInputStream()返回一個輸入流,,使用該輸入流以二進制方式讀取請求正文的內(nèi)容。javax.servlet.ServletInputStream是一個抽象類,,繼承自java.io.InputStream,。 Ø public java.lang.String getLocalAddr()返回接收到請求的網(wǎng)絡(luò)接口的IP地址,這個方法是在Servlet 2.4規(guī)范中新增的方法,。 Ø public java.lang.String getLocalName()返回接收到請求的IP接口的主機名,,這個方法是在Servlet 2.4規(guī)范中新增的方法。 Ø public int getLocalPort()返回接收到請求的網(wǎng)絡(luò)接口的IP端口號,,這個方法是在Servlet 2.4規(guī)范中新增的方法,。 Ø public java.lang.String getParameter(java.lang.String name)返回請求中name參數(shù)的值。如果name參數(shù)有多個值,,那么這個方法將返回值列表中的第一個值,。如果在請求中沒有找到這個參數(shù),這個方法將返回null,。 Ø public java.util.Enumeration getParameterNames()返回請求中包含的所有的參數(shù)的名字,。如果請求中沒有參數(shù),這個方法將返回一個空的枚舉集合,。 Ø public java.lang.String[] getParameterValues(java.lang.String name)返回請求中name參數(shù)所有的值,。如果這個參數(shù)在請求中并不存在,這個方法將返回null,。 Ø public java.lang.String getProtocol()返回請求使用的協(xié)議的名字和版本,,例如:HTTP/1.1。 Ø public java.io.BufferedReader getReader() throws java.io.IOException返回BufferedReader對象,,以字符數(shù)據(jù)方式讀取請求正文,。 Ø public java.lang.String getRemoteAddr()返回發(fā)送請求的客戶端或者最后一個代理服務(wù)器的IP地址。 Ø public java.lang.String getRemoteHost()返回發(fā)送請求的客戶端或者最后一個代理服務(wù)器的完整限定名,。 Ø public int getRemotePort()返回發(fā)送請求的客戶端或者最后一個代理服務(wù)器的IP源端口,,這個方法是在Servlet 2.4規(guī)范中新增的方法。 Ø public RequestDispatcher getRequestDispatcher(java.lang.String path)返回RequestDispatcher對象,,作為path所定位的資源的封裝,。 Ø public java.lang.String getServerName()返回請求發(fā)送到的服務(wù)器的主機名,。 Ø public int getServerPort()返回請求發(fā)送到的服務(wù)器的端口號。 Ø public void setCharacterEncoding (java.lang.String env) throws java.io.Unsupported EncodingException覆蓋在請求正文中所使用的字符編碼的名字,。 下面我們看一下ServletResponse接口中的常用方法: Ø public void flushBuffer() throws java.io.IOException強制把任何在緩存中的內(nèi)容發(fā)送到客戶端,。 Ø public int getBufferSize()返回實際用于響應(yīng)的緩存的大小。如果沒有使用緩存,,這個方法將返回0,。 Ø public java.lang.String getCharacterEncoding()返回在響應(yīng)中發(fā)送的正文所使用的字符編碼(MIME字符集)。 Ø public java.lang.String getContentType()返回在響應(yīng)中發(fā)送的正文所使用的MIME類型,。 Ø public ServletOutputStream getOutputStream() throws java.io.IOException返回ServletOutputStream對象,,用于在響應(yīng)中寫入二進制數(shù)據(jù)。javax.servlet. ServletOutputStream是一個抽象類,,繼承自java.io.OutputStream,。 Ø public java.io.PrintWriter getWriter() throws java.io.IOException返回PrintWriter對象,用于發(fā)送字符文本到客戶端,。PrintWriter對象使用getCharacterEncoding()方法返回的字符編碼,。如果沒有指定響應(yīng)的字符編碼方式,默認將使用ISO-8859-1,。 Ø public boolean isCommitted()返回一個布爾值,,指示是否已經(jīng)提交了響應(yīng)。 Ø public void reset()清除在緩存中的任何數(shù)據(jù),,包括狀態(tài)代碼和消息報頭,。如果響應(yīng)已經(jīng)被提交,這個方法將拋出IllegalStateException異常,。 Ø public void resetBuffer()清除在緩存中的響應(yīng)內(nèi)容,,保留狀態(tài)代碼和消息報頭。如果響應(yīng)已經(jīng)被提交,,這個方法將拋出IllegalStateException異常。 Ø public void setBufferSize(int size)設(shè)置響應(yīng)正文的緩存大小,。Servlet容器將使用一個緩存,,其大小至少是請求的尺寸大小。這個方法必須在響應(yīng)正文被寫入之前調(diào)用,,如果內(nèi)容已經(jīng)被寫入或者響應(yīng)對象已經(jīng)被提交,,這個方法將拋出IllegalStateException異常。 Ø public void setCharacterEncoding(java.lang.String charset)設(shè)置發(fā)送到客戶端的響應(yīng)的字符編碼,,例如,UTF-8,。 Ø public void setContentLength(int len)對于HTTP Servlet,,在響應(yīng)中,,設(shè)置內(nèi)容正文的長度,,這個方法設(shè)置HTTP Content-Length實體報頭。 Ø public void setContentType(java.lang.String type)設(shè)置要發(fā)送到客戶端的響應(yīng)的內(nèi)容類型,,此時響應(yīng)應(yīng)該還沒有提交,。給出的內(nèi)容類型可以包括字符編碼說明,例如:text/html;charset=UTF-8,。如果這個方法在getWriter()方法被調(diào)用之前調(diào)用,那么響應(yīng)的字符編碼將僅從給出的內(nèi)容類型中設(shè)置,。這個方法如果在getWriter()方法被調(diào)用之后或者在響應(yīng)被提交之后調(diào)用,,將不會設(shè)置響應(yīng)的字符編碼。在使用HTTP協(xié)議的情況中,,這個方法設(shè)置Content-Type實體報頭,。 細心的讀者可能注意到了,在上面所列舉的方法中,,有的可能會拋出IllegalStateException異常,,然而在函數(shù)聲明時,卻沒有聲明拋出此異常,,這是為什么呢,?java.lang.IllegalStateException是java.lang.RuntimeException的子類。我們知道對于RuntimeException及其派生的異常是由Java運行系統(tǒng)自動拋出并自動處理,,不需要我們?nèi)ゲ东@,,所以也就不需要在函數(shù)聲明時聲明拋出異常了。 上面所列的方法,,讀者不需要將它們都記下來,,只要大致看一下,有一個初步的印象就可以了,。關(guān)鍵是要理解請求和響應(yīng)對象能夠提供哪些方法,,讀者可以從客戶端與服務(wù)器端的交互過程來思考,想想哪些信息是需要獲取到的,。在Servlet中,,用請求對象表示的是什么信息,用響應(yīng)對象來做什么,,哪些信息應(yīng)該是從請求對象中得到,,哪些信息應(yīng)該是用響應(yīng)對象來設(shè)置,。只要理解了交互的過程及請求對象和響應(yīng)對象所起的作用,當我們需要用到某個方法時,,就可以在API文檔中進行查找,,用的次數(shù)多了,這些方法自然也就記住了,。 2.1.3 ServletConfig在javax.servlet包中,,定義了ServletConfig接口。Servlet容器使用ServletConfig對象在Servlet初始化期間向它傳遞配置信息,,一個Servlet只有一個ServletConfig對象,。在這個接口中,定義了下面四個方法: Ø public java.lang.String getInitParameter(java.lang.String name)返回名字為name的初始化參數(shù)的值,,初始化參數(shù)在web.xml配置文件中進行配置,。如果參數(shù)不存在,這個方法將返回null,。 Ø public java.util.Enumeration getInitParameterNames()返回Servlet所有初始化參數(shù)的名字的枚舉集合,。如果Servlet沒有初始化參數(shù),這個方法將返回一個空的枚舉集合,。 Ø public ServletContext getServletContext()返回Servlet上下文對象的引用,,關(guān)于ServletContext的使用,請參見第2.5節(jié),。 Ø public java.lang.String getServletName()返回Servlet實例的名字,。這個名字是在Web應(yīng)用程序的部署描述符中指定的。如果是一個沒有注冊的Servlet實例,,這個方法返回的將是Servlet的類名,。 2.1.4 一個簡單的Servlet這一節(jié)我們編寫一個最簡單的Servlet,其功能就是向客戶端輸出一個字符串“Hello World”,。實例的開發(fā)主要有下列步驟,。 Step1:編寫HelloWorldServlet類編寫一個Servlet,實際上就是編寫一個實現(xiàn)了javax.servlet.Servlet接口的類,。我們首先在%CATALINA_HOME%\webapps目錄下新建一個子目錄ch02,,然后用記事本或者UltraEdit等文本編輯工具編寫HelloWorldServlet.java源文件,將編寫好的HelloWorldServlet. java源文件放到%CATALINA_HOME%\webapps\ch02\src目錄下(讀者也可以自行選擇存放源代碼的目錄),。完整的源代碼如例2-1所示,。 例2-1 HelloWorldServlet.java package org.sunxin.ch02.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class HelloWorldServlet implements Servlet { private ServletConfig config; public void destroy(){} public ServletConfig getServletConfig() { return config; }
/** * 該方法很少使用,因此返回null即可 */ public String getServletInfo() { return null; } /** * ServletConfig對象由容器構(gòu)造,。容器在調(diào)用init()方法時,,將其作為參數(shù)傳給Servlet */ public void init(ServletConfig config) throws ServletException { this.config = config; } public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { //得到PrintWriter對象。Servlet使用輸出流來產(chǎn)生響應(yīng) PrintWriter out=res.getWriter(); //向客戶端發(fā)送字符數(shù)據(jù) out.println("Hello World"); //關(guān)閉輸出流 out.close(); } } 在Servlet中,,主要的方法是service(),,當客戶端請求到來時,,Servlet容器將調(diào)用Servlet實例的service()方法對請求進行處理,。我們在service()方法中,,首先通過ServletResponse類中的getWriter()方法調(diào)用得到一個PrintWriter類型的輸出流對象out,然后調(diào)用out對象的println()方法向客戶端發(fā)送字符串“Hello World”,,最后關(guān)閉out對象,。 Servlet容器調(diào)用Servlet實例對請求的處理過程如圖2-2所示。 圖2-2 Servlet容器調(diào)用Servlet實例對請求進行處理的全過程 Step2:編譯HelloWorldServlet.java打開命令提示符,,轉(zhuǎn)到HelloWorldServlet.java所在的目錄%CATALINA_HOME%\ webapps\ch02\src下,,然后執(zhí)行: javac -d . HelloWorldServlet.java 大多數(shù)情況下,你會看到如圖2-3所示的畫面,。 圖2-3 編譯HelloWorldServlet.java的出錯信息 產(chǎn)生這些錯誤的原因是Java編譯器沒有找到j(luò)avax.servlet包中的類,。要解決這個問題,我們需要讓Java編譯器知道Servlet API庫所在的位置,。Tomcat在其發(fā)行版中已經(jīng)包含了Servlet API庫,,是以JAR文件的形式提供的,這個JAR文件的完整路徑名是: %CATALINA_HOME%\lib\servlet-api.jar 我們只需要在系統(tǒng)的CLASSPATH環(huán)境變量下添加這個JAR文件的路徑名就可以了,。 設(shè)置CLASSPATH環(huán)境變量的方法和第1章設(shè)置JAVA_HOME環(huán)境變量的方法是一樣的,,在筆者的機器上CLASSPATH環(huán)境變量的配置如下: CLASSPATH=.;D:\OpenSource\apache-tomcat-6.0.16\lib\servlet-api.jar 關(guān)閉剛才打開的命令提示符窗口,重新打開一個新的命令提示符窗口,,進入HelloWorldServlet.java所在的目錄,,再次執(zhí)行: javac -d . HelloWorldServlet.java 生成org\sunxin\ch02\servlet目錄結(jié)構(gòu),以及在servlet子目錄中的HelloWorldServlet.class文件,。 如果你已經(jīng)安裝了J2EE SDK,,那么在安裝目錄的lib子目錄下有一個javaee.jar文件(J2EE 1.4以及之前的版本是j2ee.jar文件),其中包含了Servlet API庫,。你可以在CLASSPATH環(huán)境變量下添加javaee.jar所在的路徑名,,就不需要再配置Tomcat中的servlet-api.jar了。配置了javaee.jar后,,你還可以開發(fā)其他的J2EE應(yīng)用,。 Step3:部署HelloWorldServletServlet是Web應(yīng)用程序中的一個組件。一個Web應(yīng)用程序是由一組Servlet,、HTML頁面,、類,以及其他的資源組成的運行在Web服務(wù)器上的完整的應(yīng)用程序,,以一種結(jié)構(gòu)化的有層次的目錄形式存在,。組成Web應(yīng)用程序的這些資源文件要部署在相應(yīng)的目錄層次中,根目錄代表了整個Web應(yīng)用程序的根,。我們通常是將Web應(yīng)用程序的目錄放到%CATALINA_HOME%\webapps目錄下,,在webapps目錄下的每一個子目錄都是一個獨立的Web應(yīng)用程序,,子目錄的名字就是Web應(yīng)用程序的名字,也稱為Web應(yīng)用程序的上下文根,。用戶通過Web應(yīng)用程序的上下文根來訪問Web應(yīng)用程序中的資源,,如圖2-4所示。 如果你要新建一個Web應(yīng)用程序,,可以在webapps目錄下先建一個目錄,,在這個例子中,我們所建的目錄是ch02,,作為第一個Web應(yīng)用程序的上下文根,。Java開發(fā)的Web應(yīng)用程序需要遵照一定的目錄層次結(jié)構(gòu),在Servlet規(guī)范中定義了Web應(yīng)用程序的目錄層次結(jié)構(gòu),,如圖2-5所示,。
圖2-4 多個Web應(yīng)用程序和上下文根 圖2-5 Web應(yīng)用程序的目錄層次結(jié)構(gòu) Web應(yīng)用程序的目錄層次結(jié)構(gòu)如表2-1所示。 表2-1 Web應(yīng)用程序的目錄層次結(jié)構(gòu)
從表2-1中可以看到,,WEB-INF目錄下的classes和lib目錄都可以存放Java的類文件,,在Servlet容器運行時,Web應(yīng)用程序的類加載器將首先加載classes目錄下的,,其次才是lib目錄下的類,。如果這兩個目錄下存在同名的類,起作用的將是classes目錄下的類,。 在表2-1中,,我們還可以看到一個特殊的目錄WEB-INF,注意在書寫時不要寫錯,,所有字母都要大寫,。說這個目錄特殊,是因為這個目錄并不屬于Web應(yīng)用程序可以訪問的上下文路徑的一部分,,對客戶端來說,,這個目錄是不可見的。如果你將index.html文件放到WEB-INF目錄下,,對于客戶端是無法通過下面的方式訪問到這個文件的: http://localhost:8080/ch02/WEB-INF/index.html 不過,,WEB-INF目錄下的內(nèi)容對于Servlet代碼是可見的,在Servlet代碼中可以通過調(diào)用ServletContext對象中的getResource()或者getResourceAsStream()方法來訪問WEB-INF目錄下的資源,,也可以使用RequestDispatcher調(diào)用(參見第2.6節(jié))將WEB-INF目錄下的內(nèi)容呈現(xiàn)給客戶端,。 如果我們想要在Servlet代碼中訪問保存在文件中的配置信息,而又不希望這些配置信息被客戶端訪問到,就可以把這個文件放到WEB-INF目錄下,。 在%CATALINA_HOME%\webapps\ch02目錄下新建一個目錄WEB-INF,,進入WEB-INF目錄,新建一個classes目錄,,整個目錄結(jié)構(gòu)是: %CATALINA_HOME%\webapps\ch02\WEB-INF\classes 將編譯生成的HelloWorldServlet.class文件連同所在的包一起放到WEB-INF\classes目錄下,。 接下來,我們需要部署這個Servlet,,Web應(yīng)用程序的配置和部署是通過web.xml文件來完成的,。web.xml文件被稱為Web應(yīng)用程序的部署描述符,它可以包含如下的配置和部署信息: — ServletContext的初始化參數(shù) — Session的配置 — Servlet/JSP的定義和映射 — 應(yīng)用程序生命周期監(jiān)聽器類 — 過濾器定義和過濾器映射 — MIME類型映射 — 歡迎文件列表 — 錯誤頁面 — 語言環(huán)境和編碼映射 — 聲明式安全配置 — JSP配置 我們所編寫的web.xml文件必須是格式良好的XML,。用記事本或者UltraEdit等文本編輯工具編寫web.xml文件,內(nèi)容如例2-2所示,。 例2-2 web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java./xml/ns/javaee" xmlns:xsi="http://www./2001/XMLSchema-instance" xsi:schemaLocation="http://java./xml/ns/javaee http://java./xml/ns/javaee/web-app_2_5.xsd">
<servlet> <servlet-name>HelloWorldServlet</servlet-name> <servlet-class> org.sunxin.ch02.servlet.HelloWorldServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloWorldServlet</servlet-name> <url-pattern>/helloworld</url-pattern> </servlet-mapping> </web-app> 第一行是XML聲明,,接下來在根元素<web-app>上聲明了使用的XML Schema的版本。這段代碼是固定的,,你無須記憶它,,只要知道復(fù)制/粘貼就可以了。 注意代碼中以粗體顯示的部分,,這部分代碼使用了<servlet>和<servlet-mapping>元素,,以及它們的子元素來部署HelloWorldServlet。在web.xml文件中,,可以包含多個<servlet>和<servlet-mapping>元素,,用于部署多個Servlet。 <servlet>元素用于聲明Servlet,,<servlet-name>子元素用于指定Servlet的名字,,在同一個Web應(yīng)用程序中,每一個Servlet的名字必須是唯一的,,該元素的內(nèi)容不能為空,。<servlet-class>子元素用于指定Servlet類的完整限定名(如果有包名,要同時給出包名),。 <servlet-mapping>元素用于在Servlet和URL樣式之間定義一個映射,。它的子元素<servlet-name>指定的Servlet名字必須和<servlet>元素中的子元素<servlet-name>給出的名字相同。<url-pattern>子元素用于指定對應(yīng)于Servlet的URL路徑,,該路徑是相對于Web應(yīng)用程序上下文根的路徑,。 經(jīng)過這樣的配置之后,我們可以在瀏覽器的地址欄中輸入http://localhost:8080/ ch02/helloworld來訪問HelloWorldServlet,。當Servlet容器接收到/ch02/helloworld的請求后,,就會將發(fā)送給ch02 Web應(yīng)用程序的Context(參見第1.6節(jié)),ch02 Web應(yīng)用程序的Context首先移除該Web應(yīng)用程序上下文路徑的前綴/ch02,然后將剩余部分與web.xml文件中配置的<url-pattern>元素的內(nèi)容相比較,,找到對應(yīng)的Servlet名字為HelloWorldServlet,,再根據(jù)這個名字找到HelloWorldServlet類,進而實例化這個類,,對請求進行處理,。 將編寫好的web.xml文件保存到%CATALINA_HOME%\webapps\ch02\WEB-INF目錄下。讀者也可以將%CATALINA_HOME%\webapps\ROOT\WEB-INF目錄下的web.xml復(fù)制一份,,存放到%CATALINA_HOME%\webapps\ch02\WEB-INF目錄下,,這個文件的內(nèi)容如下: <?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java./xml/ns/javaee" xmlns:xsi="http://www./2001/XMLSchema-instance" xsi:schemaLocation="http://java./xml/ns/javaee http://java./xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>Welcome to Tomcat</display-name> <description> Welcome to Tomcat </description> </web-app> 然后編輯這個文件,添加HelloWorldServlet的配置,,如下所示: <?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java./xml/ns/javaee" xmlns:xsi="http://www./2001/XMLSchema-instance" xsi:schemaLocation="http://java./xml/ns/javaee http://java./xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>Welcome to Tomcat</display-name> <description> Welcome to Tomcat </description>
<servlet> <servlet-name>HelloWorldServlet</servlet-name> <servlet-class>org.sunxin.ch02.servlet.HelloWorldServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloWorldServlet</servlet-name> <url-pattern>/helloworld</url-pattern> </servlet-mapping> </web-app> 這個文件中其他元素的作用請參看附錄D,。 %CATALINA_HOME%\webapps\ROOT目錄是Tomcat默認的Web應(yīng)用程序的起始路徑,當我們輸入http://localhost:8080/時,,訪問的就是該目錄下的Web應(yīng)用程序資源,。如果你將本例的Servlet部署在該目錄下,訪問時只需輸入http://localhost:8080/helloworld即可,。 Step4:訪問HelloWorldServlet當部署好Servlet之后,,對客戶端來說,訪問Servlet和訪問靜態(tài)頁面沒有什么區(qū)別,。為了讓讀者對Servlet的訪問有一個較好的感性認識,,在這里我們通過兩種方式來訪問SimpleHello。 第一種方式,,我們通過telnet工具來訪問本例中的Servlet,,具體操作步驟,讀者可以參看附錄B,。 首先確保Tomcat服務(wù)器正常啟動了,。打開命令提示符,輸入 telnet localhost 8080 回車后,,輸入 GET /ch02/helloworld HTTP/1.1 Host: localhost 注意書寫與空格,。然后連續(xù)兩個回車,你將看到如圖2-6所示的畫面,。 第二種方式,,通過瀏覽器訪問Servlet,打開IE,,在地址欄中輸入: http://localhost:8080/ch02/helloworld 注意在helloworld后面不要加斜杠(/),,然后回車,你將看到如圖2-7所示的畫面,。
圖2-6 使用telnet工具與Servlet交互的信息 圖2-7 使用IE訪問HelloWorldServlet 在訪問Servlet和JSP的時候,,Servlet的名字和JSP的文件名都是區(qū)分大小寫的,,對于本例來說,如果你輸入的是HelloWorld,,那么Tomcat服務(wù)器會給出404的錯誤代碼,,提示“The requested resource (/ch02/HelloWorld) is not available”。 Web應(yīng)用程序的開發(fā)分為設(shè)計開發(fā)與配置部署兩個階段,。通過部署,,實現(xiàn)了組件與組件之間的松耦合,降低了Web應(yīng)用程序維護的難度,。在本例中,,為Servlet指定了一個名字和URL映射,,其他的組件或頁面可以使用URL來調(diào)用這個Servlet,,一旦Servlet發(fā)生了改動,,例如,,整個類被替換、重新命名等,,只需修改web.xml文件中<servlet-class>元素的內(nèi)容,,在設(shè)計開發(fā)階段確定的程序結(jié)構(gòu)與代碼不需要做任何的改動,,降低了程序維護的難度,。當然,事物都有兩面性,,在享受好處的同時,,也需要我們花費額外的時間和精力去了解和掌握部署過程。 2.1.5 GenericServlet在第2.3節(jié)中,,我們是通過實現(xiàn)Servlet接口來編寫的Servlet類,,這需要實現(xiàn)Servlet接口中定義的5個方法。為了簡化Servlet的編寫,,在javax.servlet包中提供了一個抽象的類GenericServlet,,它給出了除service()方法外的其他4個方法的簡單實現(xiàn)。GenericServlet類定義了一個通用的,、不依賴于具體協(xié)議的Servlet,,它實現(xiàn)了Servlet接口和ServletConfig接口。 Ø public abstract class GenericServlet extends java.lang.Object implements Servlet, ServletConfig, java.io.Serializable如果我們要編寫一個通用的Servlet,,只需要從GenericServlet類繼承,,并實現(xiàn)其中的抽象方法service()。 在GenericServlet類中,,定義了兩個重載的init()方法: Ø public void init(ServletConfig config) throws ServletExceptionØ public void init() throws ServletException第一個init()方法是Servlet接口中init()方法的實現(xiàn),。在這個方法中,首先將ServletConfig對象保存在一個transient實例變量中,,然后調(diào)用第二個不帶參數(shù)的init()方法,。 通常我們在編寫繼承自GenericServlet的Servlet類時,只需要重寫第二個不帶參數(shù)的init()方法就可以了。如果覆蓋了第一個init()方法,,那么應(yīng)該在子類的該方法中,,包含一句super.init(config)代碼的調(diào)用。 在GenericServlet類中還定義了下列的方法,。 Ø public java.lang.String getInitParameter(java.lang.String name)返回名字為name的初始化參數(shù)的值,,初始化參數(shù)在web.xml配置文件中進行配置。如果參數(shù)不存在,,這個方法將返回null,。 注意,這個方法只是為了方便而給出的,,它實際上是通過調(diào)用ServletConfig對象的getInitParameter()方法來得到初始化參數(shù)的,。 Ø public java.util.Enumeration getInitParameterNames()返回Servlet所有初始化參數(shù)的名字的枚舉集合。如果Servlet沒有初始化參數(shù),,這個方法將返回一個空的枚舉集合,。 注意,這個方法只是為了方便而給出的,,它實際上是通過調(diào)用ServletConfig對象的getInitParameterNames()方法來得到所有的初始化參數(shù)的名字,。 Ø public ServletContext getServletContext()返回Servlet上下文對象的引用,關(guān)于ServletContext的使用,,請參見第2.5節(jié),。 注意,這個方法只是為了方便而給出的,,它實際上是通過調(diào)用ServletConfig對象的getServletContext()方法來得到的Servlet上下文對象的引用,。 2.1.6 HttpServlet在絕大多數(shù)的網(wǎng)絡(luò)應(yīng)用中,都是客戶端(瀏覽器)通過HTTP協(xié)議去訪問服務(wù)器端的資源,,而我們所編寫的Servlet也主要是應(yīng)用于HTTP協(xié)議的請求和響應(yīng),。為了快速開發(fā)應(yīng)用于HTTP協(xié)議的Servlet類,Sun公司在javax.servlet.http包中給我們提供了一個抽象的類HttpServlet,,它繼承自GenericServlet類,,用于創(chuàng)建適合Web站點的HTTP Servlet。 public abstract class HttpServlet extends GenericServlet implements java.io.Serializable 在HttpServlet類中提供了兩個重載的service()方法: Ø public void service(ServletRequest req, ServletResponse res) throws ServletException, java.io.IOExceptionØ protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOException第一個service()方法是GenericServlet類中service()方法的實現(xiàn),。在這個方法中,,首先將req和res對象轉(zhuǎn)換為HttpServletRequest(繼承自ServletRequest接口)和HttpServletResponse(繼承自ServletResponse接口)類型,然后調(diào)用第二個service方法,,對客戶請求進行處理,。 針對HTTP1.1中定義的7種請求方法GET、POST,、HEAD,、PUT,、DELETE、TRACE和OPTIONS,,HttpServlet分別提供了7個處理方法: Ø protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOExceptionØ protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOExceptionØ protected void doHead (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOExceptionØ protected void doPut (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOExceptionØ protected void doDelete (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOExceptionØ protected void doTrace (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOExceptionØ protected void doOptions (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOException這7個方法的參數(shù)類型及異常拋出類型與HttpServlet類中的第二個重載的service()方法是一致的,。當容器接收到一個針對HttpServlet對象的請求時,調(diào)用該對象中的方法順序如下: ① 調(diào)用公共的(public)service()方法,。 ② 在公共的service()方法中,,首先將參數(shù)類型轉(zhuǎn)換為HttpServletRequest和HttpServletResponse,然后調(diào)用保護的(protected)service()方法,,將轉(zhuǎn)換后的HttpServletRequest對象和HttpServletResponse對象作為參數(shù)傳遞進去,。 ③ 在保護的service()方法中,首先調(diào)用HttpServletRequest對象的getMethod()方法,,獲取HTTP請求方法的名字,,然后根據(jù)請求方法的類型,調(diào)用相應(yīng)的doXxx ()方法,。 因此,,我們在編寫HttpServlet的派生類時,通常不需要去覆蓋service()方法,,而只需重寫相應(yīng)的doXXX()方法,。 HttpServlet類對TRACE和OPTIONS方法做了適當?shù)膶崿F(xiàn),因此我們不需要去覆蓋doTrace()和doOptions()方法,。而對于其他的5個請求方法,,HttpServlet類提供的實現(xiàn)都是返回HTTP錯誤,對于HTTP 1.0的客戶端請求,,這些方法返回狀態(tài)代碼為400的HTTP錯誤,表示客戶端發(fā)送的請求在語法上是錯誤的,。而對于HTTP 1.1的客戶端請求,,這些方法返回狀態(tài)代碼為405的HTTP錯誤,表示對于指定資源的請求方法不被允許,。這些方法都是使用javax.servlet.ServletRequest接口中的getProtocol()方法來確定協(xié)議的,。 HttpServlet雖然是抽象類,但在這個類中沒有抽象的方法,,其中所有的方法都是已經(jīng)實現(xiàn)的,。只是在這個類中對客戶請求進行處理的方法,沒有真正的實現(xiàn),,當然也不可能真正實現(xiàn),,因為對客戶請求如何進行處理,需要根據(jù)實際的應(yīng)用來決定,。我們在編寫HTTP Servlet的時候,,根據(jù)應(yīng)用的需要,,重寫其中的對客戶請求進行處理的方法即可。 2.1.7 HttpServletRequest和HttpServletResponse在javax.servlet.http包中,,定義了HttpServletRequest和HttpServletResponse這兩個接口,。這兩個接口分別繼承自javax.servlet.ServletRequest和javax.servlet.ServletResponse接口。在HttpServletRequest接口中新增的常用方法如下: Ø public java.lang.String getContextPath()返回請求URI中表示請求上下文的部分,,上下文路徑是請求URI的開始部分,。上下文路徑總是以斜杠(/)開頭,但結(jié)束沒有斜杠(/),。在默認(根)上下文中,,這個方法返回空字符串""。例如,,請求URI為“/sample/test”,,調(diào)用該方法返回路徑為“/sample”。 Ø public Cookie[] getCookies()返回客戶端在此次請求中發(fā)送的所有Cookie對象,。 Ø public java.lang.String getHeader(java.lang.String name)返回名字為name的請求報頭的值,。如果請求中沒有包含指定名字的報頭,這個方法返回null,。 Ø public java.util.Enumeration getHeaderNames()返回此次請求中包含的所有報頭名字的枚舉集合,。 Ø public java.util.Enumeration getHeaders(java.lang.String name)返回名字為name的請求報頭所有的值的枚舉集合。 Ø public java.lang.String getMethod()返回此次請求所使用的HTTP方法的名字,,例如,,GET、POST或PUT,。 Ø public java.lang.String getPathInfo()返回與客戶端發(fā)送的請求URL相聯(lián)系的額外的路徑信息,。額外的路徑信息是跟在Servlet的路徑之后、查詢字符串之前的路徑,,并以斜杠(/)字符開始,。例如,假定在web.xml文件中MyServlet類映射的URL是:/myservlet/*,,用戶請求的URL是:http://localhost:8080/ ch02/myservlet/test,,當我們在HttpServletRequest對象上調(diào)用getPathInfo()時,該方法將返回/test,。如果沒有額外的路徑信息,,getPathInfo()方法將返回null。 Ø public java.lang.String getPathTranslated()將額外的路徑信息轉(zhuǎn)換為真實的路徑,。例如,,在上面的例子中假定ch02 Web應(yīng)用程序位于D:\OpenSource\apache-tomcat-6.0.16\webapps\ch02目錄,當用戶請求http://localhost: 8080/ch02/myservlet/test時,,在請求對象上調(diào)用getPathTranslated()方法將返回D:\OpenSource\apache-tomcat-6.0.16\webapps\ch02\test,。 Ø public java.lang.String getQueryString()返回請求URL中在路徑后的查詢字符串,。如果在URL中沒有查詢字符串,該方法返回null,。例如,,有如下的請求URL: http://localhost:8080/ch02/logon.jsp?action=logon 調(diào)用getQueryString()方法將返回action=logon。 Ø public java.lang.String getRequestURI()返回請求URL中從主機名到查詢字符串之間的部分,。例如: 請求行 返回值 POST /some/path.html HTTP/1.1 /some/path.html GET http://r/a.html HTTP/1.0 /a.html HEAD /xyz?a=b HTTP/1.1 /xyz Ø public java.lang.StringBuffer getRequestURL()重新構(gòu)造客戶端用于發(fā)起請求的URL,。返回的URL包括了協(xié)議、服務(wù)器的名字,、端口號和服務(wù)器的路徑,,但是不包括查詢字符串參數(shù)。 要注意的是,,如果請求使用RequestDispatcher.forward(ServletRequest, ServletResponse)方法被轉(zhuǎn)發(fā)到另一個Servlet中,,那么你在這個Servlet中調(diào)用getRequestURL(),得到的將是獲取RequestDispatcher對象時使用的URL,,而不是原始的請求URL,。 Ø public java.lang.String getServletPath()返回請求URI中調(diào)用Servlet的部分。這部分的路徑以斜杠(/)開始,,包括了Servlet的名字或者路徑,,但是不包括額外的路徑信息和查詢字符串。例如,,假定在web.xml文件中MyServlet類映射的URL是:/myservlet/*,,用戶請求的URL是:http://localhost:8080/ ch02/myservlet/test,當我們在HttpServletRequest對象上調(diào)用getServletPath ()時,,該方法將返回/myservlet,。如果用于處理請求的Servlet與URL樣式“/*”相匹配,那么這個方法將返回空字符串(""),。 Ø public HttpSession getSession()返回和此次請求相關(guān)聯(lián)的Session,,如果沒有給客戶端分配Session,則創(chuàng)建一個新的Session,。 Ø public HttpSession getSession(boolean create)返回和此次請求相關(guān)聯(lián)的Session,如果沒有給客戶端分配Session,,而create參數(shù)為true,,則創(chuàng)建一個新的Session。如果create參數(shù)為false,,而此次請求沒有一個有效的HttpSession,,則返回null。 在HttpServletResponse接口中,,新增的常用方法如下: Ø public void addCookie(Cookie cookie)增加一個Cookie到響應(yīng)中,。這個方法可以被多次調(diào)用,,用于設(shè)置多個Cookie。 Ø public void addHeader(java.lang.String name, java.lang.String value)用給出的name和value,,增加一個響應(yīng)報頭到響應(yīng)中,。 Ø public boolean containsHeader(java.lang.String name)判斷以name為名字的響應(yīng)報頭是否已經(jīng)設(shè)置。 Ø public java.lang.String encodeRedirectURL(java.lang.String url)使用Session ID對用于重定向的url進行編碼,,以便用于sendRedirect()方法中,。如果該url不需要編碼,則返回未改變的url,。(關(guān)于這個方法的使用,,請參見第5章) Ø public java.lang.String encodeURL(java.lang.String url)使用Session ID對指定的url進行編碼。如果該url不需要編碼,,則返回未改變的url,。(關(guān)于這個方法的使用,請參見第5章) Ø public void sendError(int sc) throws java.io.IOException使用參數(shù)sc表示的狀態(tài)代碼發(fā)送一個錯誤響應(yīng)到客戶端,,同時清除緩存,。如果響應(yīng)已經(jīng)被提交,這個方法將拋出IllegalStateException異常,。 Ø public void sendError(int sc, java.lang.String msg) throws java.io.IOException使用指定的狀態(tài)代碼發(fā)送一個錯誤響應(yīng)到客戶端,。服務(wù)器默認會創(chuàng)建一個包含了指定消息的服務(wù)器端錯誤頁面作為響應(yīng),設(shè)置內(nèi)容類型為“text/html”,。如果Web應(yīng)用程序已經(jīng)聲明了對應(yīng)于指定狀態(tài)代碼的錯誤頁面,,則服務(wù)器會將這個頁面發(fā)送給客戶端,而不理會參數(shù)msg指定的錯誤消息,。如果響應(yīng)已經(jīng)被提交,,這個方法將拋出IllegalStateException異常。 Ø public void sendRedirect(java.lang.String location) throws java.io.IOException發(fā)送一個臨時的重定向響應(yīng)到客戶端,,讓客戶端訪問新的URL,。如果指定的位置是相對URL,Servlet容器在發(fā)送響應(yīng)到客戶端之前,,必須將相對URL轉(zhuǎn)換為絕對URL,。如果響應(yīng)已經(jīng)被提交,這個方法將拋出IllegalStateException異常,。 Ø public void setHeader(java.lang.String name, java.lang.String value)用給出的name和value,,設(shè)置一個響應(yīng)報頭。如果這個報頭已經(jīng)被設(shè)置,,新的值將覆蓋先前的值,。 Ø public void setStatus(int sc)為響應(yīng)設(shè)置狀態(tài)代碼。 此外,,在HttpServletResponse接口中,,還定義了一組整型的靜態(tài)常量,,用于表示HTTP錯誤代碼,這些錯誤代碼對應(yīng)于HTTP/1.1中的錯誤代碼,。關(guān)于這些錯誤代碼常量,,請參看HttpServletResponse接口的API文檔。 要想更好地理解HttpServletRequest和HttpServletResponse的使用,,應(yīng)該結(jié)合HTTP協(xié)議來看,,彼此對照。HTTP協(xié)議的介紹參見附錄B,。 |
|