— 了解如何通過實現(xiàn)Servlet接口來編寫Servlet
— 掌握ServletRequest和ServletResponse接口
— 掌握ServletConfig接口
— 掌握GenericServlet和HttpServlet抽象類
— 掌握HttpServletRequest和HttpServletResponse接口
— 掌握Servlet開發(fā)中一些方法和技巧的使用
— 熟悉Servlet異常
— 掌握Servlet上下文
— 掌握RequestDispatcher對象的使用
— 區(qū)分sendRedirect()和forward()方法的使用
從這一章開始,我 們將詳細(xì)介紹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)用程序)。它運(yùn)行在客戶端的瀏覽器中。Java Applet與Java Servlet有以下一些共同點。
— 它們都不是獨(dú)立的應(yīng)用程序,都沒有main()方法。
— 它們都不是由用戶或程序員直接調(diào)用,而是生存在容器中,由容器管理。Applet運(yùn)行在瀏覽器中,Servlet運(yùn)行在Servlet容器中。
— 它們都有生命周期,都包含了init()和destroy()方法。
當(dāng)然,Applet與Servlet也有不同點。
— Applet具有圖形界面,運(yùn)行在客戶端的瀏覽器中。
— Servlet沒有圖形界面,運(yùn)行在服務(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運(yùn)行環(huán)境的ServletContext對象,使用該對象,Servlet可以和它的Servlet容器進(jìn)行通信。
— service():容器調(diào)用service()方法來處理客戶端的請求。要注意的是,在service()方法被容器調(diào)用之前,必須確保init()方法正確完成。容器會構(gòu)造一個表示客戶端請求信息的請求對象(類型為ServletRequest)和一個用于對客戶端進(jìn)行響應(yīng)的響應(yīng)對象(類型為ServletResponse)作為參數(shù)傳遞給service()方法。在service()方法中,Servlet對象通過ServletRequest對象得到客戶端的相關(guān)信息和請求信息,在對請求進(jìn)行處理后,調(diào)用ServletResponse對象的方法設(shè)置響應(yīng)信息。
— destroy():當(dāng)容器檢測到一個Servlet對象應(yīng)該從服務(wù)中被移除的時候,容器會調(diào)用該對象的destroy()方法,以便讓Servlet對象可以釋放它所使用的資源,保存數(shù)據(jù)到持久存儲設(shè)備中,例如,將內(nèi)存中的數(shù)據(jù)保存到數(shù)據(jù)庫中,關(guān)閉數(shù)據(jù)庫的連接等。當(dāng)需要釋放內(nèi)存或者容器關(guān)閉時,容器就會調(diào)用Servlet對象的destroy()方法。在Servlet容器調(diào)用destroy()方法前,如果還有其他的線程正在service()方法中執(zhí)行,容器會等待這些線程執(zhí)行完畢或等待服務(wù)器設(shè)定的超時值到達(dá)。一旦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)該是純文本字符串,而不是任何類型的標(biāo)記(HTML、XML等)。
Servlet API包含在J2EE中,如果要查看Servlet API的文檔,你需要下載J2EE(從J2EE 1.5開始改名為Java EE 5)SDK,Java EE 5 SDK的下載地址如下:http://java.sun.com/javaee/downloads/?intcmp=1282。
2.1.2 ServletRequest和ServletResponse
Servlet由Servlet容器來管理,當(dāng)客戶請求到來時,容器創(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,那么相當(dāng)于調(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()
返回一個輸入流,使用該輸入流以二進(jìn)制方式讀取請求正文的內(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接口的主機(jī)名,這個方法是在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ù)器的主機(jī)名。
Ø 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
強(qiáng)制把任何在緩存中的內(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)中寫入二進(jìn)制數(shù)據(jù)。javax.servlet. ServletOutputStream是一個抽象類,繼承自java.io.OutputStream。
Ø public java.io.PrintWriter getWriter() throws java.io.IOException
返回PrintWriter對象,用于發(fā)送字符文本到客戶端。PrintWriter對象使用getCharacterEncoding()方法返回的字符編碼。如果沒有指定響應(yīng)的字符編碼方式,默認(rèn)將使用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實體報頭。
細(xì)心的讀者可能注意到了,在上面所列舉的方法中,有的可能會拋出IllegalStateException異常,然而在函數(shù)聲明時,卻沒有聲明拋出此異常,這是為什么呢?java.lang.IllegalStateException是java.lang.RuntimeException的子類。我們知道對于RuntimeException及其派生的異常是由Java運(yùn)行系統(tǒng)自動拋出并自動處理,不需要我們?nèi)ゲ东@,所以也就不需要在函數(shù)聲明時聲明拋出異常了。
上面所列的方法,讀者不需要將它們都記下來,只要大致看一下,有一個初步的印象就可以了。關(guān)鍵是要理解請求和響應(yīng)對象能夠提供哪些方法,讀者可以從客戶端與服務(wù)器端的交互過程來思考,想想哪些信息是需要獲取到的。在Servlet中,用請求對象表示的是什么信息,用響應(yīng)對象來做什么,哪些信息應(yīng)該是從請求對象中得到,哪些信息應(yīng)該是用響應(yīng)對象來設(shè)置。只要理解了交互的過程及請求對象和響應(yīng)對象所起的作用,當(dāng)我們需要用到某個方法時,就可以在API文檔中進(jìn)行查找,用的次數(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配置文件中進(jìn)行配置。如果參數(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(),當(dāng)客戶端請求到來時,Servlet容器將調(diào)用Servlet實例的service()方法對請求進(jìn)行處理。我們在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實例對請求進(jìn)行處理的全過程
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)境變量的方法是一樣的,在筆者的機(jī)器上CLASSPATH環(huán)境變量的配置如下:
CLASSPATH=.;D:\OpenSource\apache-tomcat-6.0.16\lib\servlet-api.jar
關(guān)閉剛才打開的命令提示符窗口,重新打開一個新的命令提示符窗口,進(jì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:部署HelloWorldServlet
Servlet是Web應(yīng)用程序中的一個組件。一個Web應(yīng)用程序是由一組Servlet、HTML頁面、類,以及其他的資源組成的運(yùn)行在Web服務(wù)器上的完整的應(yīng)用程序,以一種結(jié)構(gòu)化的有層次的目錄形式存在。組成Web應(yīng)用程序的這些資源文件要部署在相應(yīng)的目錄層次中,根目錄代表了整個Web應(yīng)用程序的根。我們通常是將Web應(yīng)用程序的目錄放到%CATALINA_HOME%\webapps目錄下,在webapps目錄下的每一個子目錄都是一個獨(dú)立的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)
目 錄
描 述
\ch02
Web應(yīng)用程序的根目錄,屬于此Web應(yīng)用程序的所有文件都存放在這個目錄下
\ch02\WEB-INF
存放Web應(yīng)用程序的部署描述符文件web.xml
\ch02\WEB-INF\classes
存放Servlet和其他有用的類文件
\ch02\WEB-INF\lib
存放Web應(yīng)用程序需要用到的JAR文件,這些JAR文件中可以包含Servlet、Bean和其他有用的類文件
\ch02\WEB-INF\web.xml
web.xml文件包含Web應(yīng)用程序的配置和部署信息
從表2-1中可以看到,WEB-INF目錄下的classes和lib目錄都可以存放Java的類文件,在Servlet容器運(yùn)行時,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,進(jìn)入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.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/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。當(dāng)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類,進(jìn)而實例化這個類,對請求進(jìn)行處理。
將編寫好的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.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/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.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/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默認(rèn)的Web應(yīng)用程序的起始路徑,當(dāng)我們輸入http://localhost:8080/時,訪問的就是該目錄下的Web應(yīng)用程序資源。如果你將本例的Servlet部署在該目錄下,訪問時只需輸入http://localhost:8080/helloworld即可。
Step4:訪問HelloWorldServlet
當(dāng)部署好Servlet之后,對客戶端來說,訪問Servlet和訪問靜態(tài)頁面沒有什么區(qū)別。為了讓讀者對Servlet的訪問有一個較好的感性認(rèn)識,在這里我們通過兩種方式來訪問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)用程序維護(hù)的難度。在本例中,為Servlet指定了一個名字和URL映射,其他的組件或頁面可以使用URL來調(diào)用這個Servlet,一旦Servlet發(fā)生了改動,例如,整個類被替換、重新命名等,只需修改web.xml文件中<servlet-class>元素的內(nèi)容,在設(shè)計開發(fā)階段確定的程序結(jié)構(gòu)與代碼不需要做任何的改動,降低了程序維護(hù)的難度。當(dāng)然,事物都有兩面性,在享受好處的同時,也需要我們花費(fèi)額外的時間和精力去了解和掌握部署過程。
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配置文件中進(jìn)行配置。如果參數(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方法,對客戶請求進(jìn)行處理。
針對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()方法是一致的。當(dāng)容器接收到一個針對HttpServlet對象的請求時,調(diào)用該對象中的方法順序如下:
① 調(diào)用公共的(public)service()方法。
② 在公共的service()方法中,首先將參數(shù)類型轉(zhuǎn)換為HttpServletRequest和HttpServletResponse,然后調(diào)用保護(hù)的(protected)service()方法,將轉(zhuǎn)換后的HttpServletRequest對象和HttpServletResponse對象作為參數(shù)傳遞進(jìn)去。
③ 在保護(hù)的service()方法中,首先調(diào)用HttpServletRequest對象的getMethod()方法,獲取HTTP請求方法的名字,然后根據(jù)請求方法的類型,調(diào)用相應(yīng)的doXxx ()方法。
因此,我們在編寫HttpServlet的派生類時,通常不需要去覆蓋service()方法,而只需重寫相應(yīng)的doXXX()方法。
HttpServlet類對TRACE和OPTIONS方法做了適當(dāng)?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)的。只是在這個類中對客戶請求進(jìn)行處理的方法,沒有真正的實現(xiàn),當(dāng)然也不可能真正實現(xiàn),因為對客戶請求如何進(jìn)行處理,需要根據(jù)實際的應(yīng)用來決定。我們在編寫HTTP Servlet的時候,根據(jù)應(yīng)用的需要,重寫其中的對客戶請求進(jìn)行處理的方法即可。
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é)束沒有斜杠(/)。在默認(rèn)(根)上下文中,這個方法返回空字符串""。例如,請求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,當(dāng)我們在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目錄,當(dāng)用戶請求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中從主機(jī)名到查詢字符串之間的部分。例如:
請求行 返回值
POST /some/path.html HTTP/1.1 /some/path.html
GET http://foo.bar/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,當(dāng)我們在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進(jìn)行編碼,以便用于sendRedirect()方法中。如果該url不需要編碼,則返回未改變的url。(關(guān)于這個方法的使用,請參見第5章)
Ø public java.lang.String encodeURL(java.lang.String url)
使用Session ID對指定的url進(jìn)行編碼。如果該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ù)器默認(rèn)會創(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。