官术网_书友最值得收藏!

2.1 Servlet API

這一節我們主要介紹一下開發Servlet需要用到的主要接口和類,這些接口和類的UML類圖如圖2-1所示。

圖2-1 Servlet API中主要的接口與類的UML類圖

2.1.1 Servlet接口

在Java語言中,我們已經了解了Java Applet(Java小應用程序)。它運行在客戶端的瀏覽器中。Java Applet與Java Servlet有以下一些共同點。

·它們都不是獨立的應用程序,都沒有main()方法。

·它們都不是由用戶或程序員直接調用,而是生存在容器中,由容器管理。Applet運行在瀏覽器中,Servlet運行在Servlet容器中。

·它們都有生命周期,都包含了init()和destroy()方法。

當然,Applet與Servlet也有不同點。

·Applet具有圖形界面,運行在客戶端的瀏覽器中。

·Servlet沒有圖形界面,運行在服務器端的Servlet容器中。

要編寫一個Applet,需要從java.applet.Applet類派生一個子類;和Applet類似,要編寫一個Servlet,需要實現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容器會調用init()方法,來初始化該對象,主要是為了讓Servlet對象在處理客戶請求前可以完成一些初始化的工作,例如,建立數據庫的連接,獲取配置信息等。對于每一個Servlet實例,init()方法只能被調用一次。init()方法有一個類型為ServletConfig的參數,Servlet容器通過這個參數向Servlet傳遞配置信息。Servlet使用ServletConfig對象從Web應用程序的配置信息中獲取以名-值對形式提供的初始化參數。另外,在Servlet中,還可以通過ServletConfig對象獲取描述Servlet運行環境的ServletContext對象,使用該對象,Servlet可以和它的Servlet容器進行通信。

·service():容器調用service()方法來處理客戶端的請求。要注意的是,在service()方法被容器調用之前,必須確保init()方法正確完成。容器會構造一個表示客戶端請求信息的請求對象(類型為ServletRequest)和一個用于對客戶端進行響應的響應對象(類型為ServletResponse)作為參數傳遞給service()方法。在service()方法中,Servlet對象通過ServletRequest對象得到客戶端的相關信息和請求信息,在對請求進行處理后,調用ServletResponse對象的方法設置響應信息。

·destroy():當容器檢測到一個Servlet對象應該從服務中被移除的時候,容器會調用該對象的destroy()方法,以便讓Servlet對象可以釋放它所使用的資源,保存數據到持久存儲設備中,例如,將內存中的數據保存到數據庫中,關閉數據庫的連接等。當需要釋放內存或者容器關閉時,容器就會調用Servlet對象的destroy()方法。在Servlet容器調用destroy()方法前,如果還有其他的線程正在service()方法中執行,容器會等待這些線程執行完畢或等待服務器設定的超時值到達。一旦Servlet對象的destroy()方法被調用,容器不會再把其他的請求發送給該對象。如果需要該Servlet再次為客戶端服務,容器將會重新產生一個Servlet對象來處理客戶端的請求。在destroy()方法調用之后,容器會釋放這個Servlet對象,在隨后的時間內,該對象會被Java的垃圾收集器所回收。

·getServletConfig():該方法返回容器調用init()方法時傳遞給Servlet對象的ServletConfig對象,ServletConfig對象包含了Servlet的初始化參數。

·getServletInfo():返回一個String類型的字符串,其中包括了關于Servlet的信息,例如,作者、版本和版權。該方法返回的應該是純文本字符串,而不是任何類型的標記(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容器來管理,當客戶請求到來時,容器創建一個ServletRequest對象,封裝請求數據,同時創建一個ServletResponse對象,封裝響應數據。這兩個對象將被容器作為service()方法的參數傳遞給Servlet,Servlet利用ServletRequest對象獲取客戶端發來的請求數據,利用ServletResponse對象發送響應數據。

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的屬性。如果第二個參數o為null,那么相當于調用removeAttribute(name)。

public java.lang.String getCharacterEncoding()

返回請求正文使用的字符編碼的名字。如果請求沒有指定字符編碼,這個方法將返回null。

public int getContentLength()

以字節為單位,返回請求正文的長度。如果長度不可知,這個方法將返回-1。

public java.lang.String getContentType()

返回請求正文的MIME類型。如果類型不可知,這個方法將返回null。

public ServletInputStream getInputStream()

返回一個輸入流,使用該輸入流以二進制方式讀取請求正文的內容。javax.servlet.ServletInputStream是一個抽象類,繼承自java.io.InputStream。

public java.lang.String getLocalAddr()

返回接收到請求的網絡接口的IP地址,這個方法是在Servlet 2.4規范中新增的方法。

public java.lang.String getLocalName()

返回接收到請求的IP接口的主機名,這個方法是在Servlet 2.4規范中新增的方法。

public int getLocalPort()

返回接收到請求的網絡接口的IP端口號,這個方法是在Servlet 2.4規范中新增的方法。

public java.lang.String getParameter(java.lang.String name)

返回請求中name參數的值。如果name參數有多個值,那么這個方法將返回值列表中的第一個值。如果在請求中沒有找到這個參數,這個方法將返回null。

public java.util.Enumeration getParameterNames()

返回請求中包含的所有的參數的名字。如果請求中沒有參數,這個方法將返回一個空的枚舉集合。

public java.lang.String[] getParameterValues(java.lang.String name)

返回請求中name參數所有的值。如果這個參數在請求中并不存在,這個方法將返回null。

public java.lang.String getProtocol()

返回請求使用的協議的名字和版本,例如:HTTP/1.1。

public java.io.BufferedReader getReader() throws java.io.IOException

返回BufferedReader對象,以字符數據方式讀取請求正文。

public java.lang.String getRemoteAddr()

返回發送請求的客戶端或者最后一個代理服務器的IP地址。

public java.lang.String getRemoteHost()

返回發送請求的客戶端或者最后一個代理服務器的完整限定名。

public int getRemotePort()

返回發送請求的客戶端或者最后一個代理服務器的IP源端口,這個方法是在Servlet 2.4規范中新增的方法。

public RequestDispatcher getRequestDispatcher(java.lang.String path)

返回RequestDispatcher對象,作為path所定位的資源的封裝。

public java.lang.String getServerName()

返回請求發送到的服務器的主機名。

public int getServerPort()

返回請求發送到的服務器的端口號。

public void setCharacterEncoding (java.lang.String env) throws java.io.Unsupported EncodingException

覆蓋在請求正文中所使用的字符編碼的名字。

下面我們看一下ServletResponse接口中的常用方法:

public void flushBuffer() throws java.io.IOException

強制把任何在緩存中的內容發送到客戶端。

public int getBufferSize()

返回實際用于響應的緩存的大小。如果沒有使用緩存,這個方法將返回0。

public java.lang.String getCharacterEncoding()

返回在響應中發送的正文所使用的字符編碼(MIME字符集)。

public java.lang.String getContentType()

返回在響應中發送的正文所使用的MIME類型。

public ServletOutputStream getOutputStream() throws java.io.IOException

返回ServletOutputStream對象,用于在響應中寫入二進制數據。javax.servlet. ServletOutputStream是一個抽象類,繼承自java.io.OutputStream。

public java.io.PrintWriter getWriter() throws java.io.IOException

返回PrintWriter對象,用于發送字符文本到客戶端。PrintWriter對象使用getCharacterEncoding()方法返回的字符編碼。如果沒有指定響應的字符編碼方式,默認將使用ISO-8859-1。

public boolean isCommitted()

返回一個布爾值,指示是否已經提交了響應。

public void reset()

清除在緩存中的任何數據,包括狀態代碼和消息報頭。如果響應已經被提交,這個方法將拋出IllegalStateException異常。

public void resetBuffer()

清除在緩存中的響應內容,保留狀態代碼和消息報頭。如果響應已經被提交,這個方法將拋出IllegalStateException異常。

public void setBufferSize(int size)

設置響應正文的緩存大小。Servlet容器將使用一個緩存,其大小至少是請求的尺寸大小。這個方法必須在響應正文被寫入之前調用,如果內容已經被寫入或者響應對象已經被提交,這個方法將拋出IllegalStateException異常。

public void setCharacterEncoding(java.lang.String charset)

設置發送到客戶端的響應的字符編碼,例如,UTF-8。

public void setContentLength(int len)

對于HTTP Servlet,在響應中,設置內容正文的長度,這個方法設置HTTP Content-Length實體報頭。

public void setContentType(java.lang.String type)

設置要發送到客戶端的響應的內容類型,此時響應應該還沒有提交。給出的內容類型可以包括字符編碼說明,例如:text/html;charset=UTF-8。如果這個方法在getWriter()方法被調用之前調用,那么響應的字符編碼將僅從給出的內容類型中設置。這個方法如果在getWriter()方法被調用之后或者在響應被提交之后調用,將不會設置響應的字符編碼。在使用HTTP協議的情況中,這個方法設置Content-Type實體報頭。

細心的讀者可能注意到了,在上面所列舉的方法中,有的可能會拋出IllegalStateException異常,然而在函數聲明時,卻沒有聲明拋出此異常,這是為什么呢?java.lang.IllegalStateException是java.lang.RuntimeException的子類。我們知道對于RuntimeException及其派生的異常是由Java運行系統自動拋出并自動處理,不需要我們去捕獲,所以也就不需要在函數聲明時聲明拋出異常了。

上面所列的方法,讀者不需要將它們都記下來,只要大致看一下,有一個初步的印象就可以了。關鍵是要理解請求和響應對象能夠提供哪些方法,讀者可以從客戶端與服務器端的交互過程來思考,想想哪些信息是需要獲取到的。在Servlet中,用請求對象表示的是什么信息,用響應對象來做什么,哪些信息應該是從請求對象中得到,哪些信息應該是用響應對象來設置。只要理解了交互的過程及請求對象和響應對象所起的作用,當我們需要用到某個方法時,就可以在API文檔中進行查找,用的次數多了,這些方法自然也就記住了。

2.1.3 ServletConfig

在javax.servlet包中,定義了ServletConfig接口。Servlet容器使用ServletConfig對象在Servlet初始化期間向它傳遞配置信息,一個Servlet只有一個ServletConfig對象。在這個接口中,定義了下面四個方法:

public java.lang.String getInitParameter(java.lang.String name)

返回名字為name的初始化參數的值,初始化參數在web.xml配置文件中進行配置。如果參數不存在,這個方法將返回null。

public java.util.Enumeration getInitParameterNames()

返回Servlet所有初始化參數的名字的枚舉集合。如果Servlet沒有初始化參數,這個方法將返回一個空的枚舉集合。

public ServletContext getServletContext()

返回Servlet上下文對象的引用,關于ServletContext的使用,請參見第2.5節。

public java.lang.String getServletName()

返回Servlet實例的名字。這個名字是在Web應用程序的部署描述符中指定的。如果是一個沒有注冊的Servlet實例,這個方法返回的將是Servlet的類名。

2.1.4 一個簡單的Servlet

這一節我們編寫一個最簡單的Servlet,其功能就是向客戶端輸出一個字符串“Hello World”。實例的開發主要有下列步驟。

開發步驟

Step1:編寫HelloWorldServlet類

編寫一個Servlet,實際上就是編寫一個實現了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對象由容器構造。容器在調用init()方法時,將其作為參數傳給Servlet
            */
           public void init(ServletConfig config) throws ServletException
           {
                this.config = config;
           }
           public void service(ServletRequest req, ServletResponse res)
                    throws ServletException, IOException
           {
                //得到PrintWriter對象。Servlet使用輸出流來產生響應
                PrintWriter out=res.getWriter();
                //向客戶端發送字符數據
                out.println("Hello World");
                //關閉輸出流
                out.close();
           }
        }

在Servlet中,主要的方法是service(),當客戶端請求到來時,Servlet容器將調用Servlet實例的service()方法對請求進行處理。我們在service()方法中,首先通過ServletResponse類中的getWriter()方法調用得到一個PrintWriter類型的輸出流對象out,然后調用out對象的println()方法向客戶端發送字符串“Hello World”,最后關閉out對象。

Servlet容器調用Servlet實例對請求的處理過程如圖2-2所示。

圖2-2 Servlet容器調用Servlet實例對請求進行處理的全過程

Step2:編譯HelloWorldServlet.java

打開命令提示符,轉到HelloWorldServlet.java所在的目錄%CATALINA_HOME%\webapps\ch02\src下,然后執行:

        javac -d . HelloWorldServlet.java

大多數情況下,你會看到如圖2-3所示的畫面。

圖2-3 編譯HelloWorldServlet.java的出錯信息

產生這些錯誤的原因是Java編譯器沒有找到javax.servlet包中的類。要解決這個問題,我們需要讓Java編譯器知道Servlet API庫所在的位置。Tomcat在其發行版中已經包含了Servlet API庫,是以JAR文件的形式提供的,這個JAR文件的完整路徑名是:

        %CATALINA_HOME%\lib\servlet-api.jar

我們只需要在系統的CLASSPATH環境變量下添加這個JAR文件的路徑名就可以了。

設置CLASSPATH環境變量的方法和第1章設置JAVA_HOME環境變量的方法是一樣的,在筆者的機器上CLASSPATH環境變量的配置如下:

        CLASSPATH=.;D:\OpenSource\apache-tomcat-6.0.16\lib\servlet-api.jar

關閉剛才打開的命令提示符窗口,重新打開一個新的命令提示符窗口,進入HelloWorldServlet.java所在的目錄,再次執行:

        javac -d . HelloWorldServlet.java

生成org\sunxin\ch02\servlet目錄結構,以及在servlet子目錄中的HelloWorldServlet.class文件。

如果你已經安裝了J2EE SDK,那么在安裝目錄的lib子目錄下有一個javaee.jar文件(J2EE 1.4以及之前的版本是j2ee.jar文件),其中包含了Servlet API庫。你可以在CLASSPATH環境變量下添加javaee.jar所在的路徑名,就不需要再配置Tomcat中的servlet-api.jar了。配置了javaee.jar后,你還可以開發其他的J2EE應用。

Step3:部署HelloWorldServlet

Servlet是Web應用程序中的一個組件。一個Web應用程序是由一組Servlet、HTML頁面、類,以及其他的資源組成的運行在Web服務器上的完整的應用程序,以一種結構化的有層次的目錄形式存在。組成Web應用程序的這些資源文件要部署在相應的目錄層次中,根目錄代表了整個Web應用程序的根。我們通常是將Web應用程序的目錄放到%CATALINA_HOME%\webapps目錄下,在webapps目錄下的每一個子目錄都是一個獨立的Web應用程序,子目錄的名字就是Web應用程序的名字,也稱為Web應用程序的上下文根。用戶通過Web應用程序的上下文根來訪問Web應用程序中的資源,如圖2-4所示。

圖2-4 多個Web應用程序和上下文根

如果你要新建一個Web應用程序,可以在webapps目錄下先建一個目錄,在這個例子中,我們所建的目錄是ch02,作為第一個Web應用程序的上下文根。Java開發的Web應用程序需要遵照一定的目錄層次結構,在Servlet規范中定義了Web應用程序的目錄層次結構,如圖2-5所示。

圖2-5 Web應用程序的目錄層次結構

Web應用程序的目錄層次結構如表2-1所示。

表2-1 Web應用程序的目錄層次結構

從表2-1中可以看到,WEB-INF目錄下的classes和lib目錄都可以存放Java的類文件,在Servlet容器運行時,Web應用程序的類加載器將首先加載classes目錄下的,其次才是lib目錄下的類。如果這兩個目錄下存在同名的類,起作用的將是classes目錄下的類。

在表2-1中,我們還可以看到一個特殊的目錄WEB-INF,注意在書寫時不要寫錯,所有字母都要大寫。說這個目錄特殊,是因為這個目錄并不屬于Web應用程序可以訪問的上下文路徑的一部分,對客戶端來說,這個目錄是不可見的。如果你將index.html文件放到WEB-INF目錄下,對于客戶端是無法通過下面的方式訪問到這個文件的:

      http://localhost:8080/ch02/WEB-INF/index.html

不過,WEB-INF目錄下的內容對于Servlet代碼是可見的,在Servlet代碼中可以通過調用ServletContext對象中的getResource()或者getResourceAsStream()方法來訪問WEB-INF目錄下的資源,也可以使用RequestDispatcher調用(參見第2.6節)將WEB-INF目錄下的內容呈現給客戶端。

如果我們想要在Servlet代碼中訪問保存在文件中的配置信息,而又不希望這些配置信息被客戶端訪問到,就可以把這個文件放到WEB-INF目錄下。

在%CATALINA_HOME%\webapps\ch02目錄下新建一個目錄WEB-INF,進入WEB-INF目錄,新建一個classes目錄,整個目錄結構是:

      %CATALINA_HOME%\webapps\ch02\WEB-INF\classes

將編譯生成的HelloWorldServlet.class文件連同所在的包一起放到WEB-INF\classes目錄下。

接下來,我們需要部署這個Servlet,Web應用程序的配置和部署是通過web.xml文件來完成的。web.xml文件被稱為Web應用程序的部署描述符,它可以包含如下的配置和部署信息:

·ServletContext的初始化參數

·Session的配置

·Servlet/JSP的定義和映射

·應用程序生命周期監聽器類

·過濾器定義和過濾器映射

·MIME類型映射

·歡迎文件列表

·錯誤頁面

·語言環境和編碼映射

·聲明式安全配置

·JSP配置

我們所編寫的web.xml文件必須是格式良好的XML。用記事本或者UltraEdit等文本編輯工具編寫web.xml文件,內容如例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的版本。這段代碼是固定的,你無須記憶它,只要知道復制/粘貼就可以了。

注意代碼中以粗體顯示的部分,這部分代碼使用了<servlet>和<servlet-mapping>元素,以及它們的子元素來部署HelloWorldServlet。在web.xml文件中,可以包含多個<servlet>和<servlet-mapping>元素,用于部署多個Servlet。

<servlet>元素用于聲明Servlet,<servlet-name>子元素用于指定Servlet的名字,在同一個Web應用程序中,每一個Servlet的名字必須是唯一的,該元素的內容不能為空。<servlet-class>子元素用于指定Servlet類的完整限定名(如果有包名,要同時給出包名)。

<servlet-mapping>元素用于在Servlet和URL樣式之間定義一個映射。它的子元素<servlet-name>指定的Servlet名字必須和<servlet>元素中的子元素<servlet-name>給出的名字相同。<url-pattern>子元素用于指定對應于Servlet的URL路徑,該路徑是相對于Web應用程序上下文根的路徑。

經過這樣的配置之后,我們可以在瀏覽器的地址欄中輸入http://localhost:8080/ch02/helloworld來訪問HelloWorldServlet。當Servlet容器接收到/ch02/helloworld的請求后,就會將發送給ch02 Web應用程序的Context(參見第1.6節),ch02 Web應用程序的Context首先移除該Web應用程序上下文路徑的前綴/ch02,然后將剩余部分與web.xml文件中配置的<url-pattern>元素的內容相比較,找到對應的Servlet名字為HelloWorldServlet,再根據這個名字找到HelloWorldServlet類,進而實例化這個類,對請求進行處理。

將編寫好的web.xml文件保存到%CATALINA_HOME%\webapps\ch02\WEB-INF目錄下。讀者也可以將%CATALINA_HOME%\webapps\ROOT\WEB-INF目錄下的web.xml復制一份,存放到%CATALINA_HOME%\webapps\ch02\WEB-INF目錄下,這個文件的內容如下:

        <?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默認的Web應用程序的起始路徑,當我們輸入http://localhost:8080/時,訪問的就是該目錄下的Web應用程序資源。如果你將本例的Servlet部署在該目錄下,訪問時只需輸入http://localhost:8080/helloworld即可。

Step4:訪問HelloWorldServlet

當部署好Servlet之后,對客戶端來說,訪問Servlet和訪問靜態頁面沒有什么區別。為了讓讀者對Servlet的訪問有一個較好的感性認識,在這里我們通過兩種方式來訪問SimpleHello。

第一種方式,我們通過telnet工具來訪問本例中的Servlet,具體操作步驟,讀者可以參看附錄B。

首先確保Tomcat服務器正常啟動了。打開命令提示符,輸入

        telnet localhost 8080

回車后,輸入

        GET /ch02/helloworld HTTP/1.1
        Host: localhost

注意書寫與空格。然后連續兩個回車,你將看到如圖2-6所示的畫面。

圖2-6 使用telnet工具與Servlet交互的信息

第二種方式,通過瀏覽器訪問Servlet,打開IE,在地址欄中輸入:

        http://localhost:8080/ch02/helloworld

注意在helloworld后面不要加斜杠(/),然后回車,你將看到如圖2-7所示的畫面。

圖2-7 使用IE訪問HelloWorldServlet

在訪問Servlet和JSP的時候,Servlet的名字和JSP的文件名都是區分大小寫的,對于本例來說,如果你輸入的是HelloWorld,那么Tomcat服務器會給出404的錯誤代碼,提示“The requested resource (/ch02/HelloWorld) is not available”。

Web應用程序的開發分為設計開發與配置部署兩個階段。通過部署,實現了組件與組件之間的松耦合,降低了Web應用程序維護的難度。在本例中,為Servlet指定了一個名字和URL映射,其他的組件或頁面可以使用URL來調用這個Servlet,一旦Servlet發生了改動,例如,整個類被替換、重新命名等,只需修改web.xml文件中<servlet-class>元素的內容,在設計開發階段確定的程序結構與代碼不需要做任何的改動,降低了程序維護的難度。當然,事物都有兩面性,在享受好處的同時,也需要我們花費額外的時間和精力去了解和掌握部署過程。

2.1.5 GenericServlet

在第2.3節中,我們是通過實現Servlet接口來編寫的Servlet類,這需要實現Servlet接口中定義的5個方法。為了簡化Servlet的編寫,在javax.servlet包中提供了一個抽象的類GenericServlet,它給出了除service()方法外的其他4個方法的簡單實現。GenericServlet類定義了一個通用的、不依賴于具體協議的Servlet,它實現了Servlet接口和ServletConfig接口。

public abstract class GenericServlet extends java.lang.Object implements Servlet, ServletConfig, java.io.Serializable

如果我們要編寫一個通用的Servlet,只需要從GenericServlet類繼承,并實現其中的抽象方法service()。

在GenericServlet類中,定義了兩個重載的init()方法:

public void init(ServletConfig config) throws ServletException

public void init() throws ServletException

第一個init()方法是Servlet接口中init()方法的實現。在這個方法中,首先將ServletConfig對象保存在一個transient實例變量中,然后調用第二個不帶參數的init()方法。

通常我們在編寫繼承自GenericServlet的Servlet類時,只需要重寫第二個不帶參數的init()方法就可以了。如果覆蓋了第一個init()方法,那么應該在子類的該方法中,包含一句super.init(config)代碼的調用。

在GenericServlet類中還定義了下列的方法。

public java.lang.String getInitParameter(java.lang.String name)

返回名字為name的初始化參數的值,初始化參數在web.xml配置文件中進行配置。如果參數不存在,這個方法將返回null。

注意,這個方法只是為了方便而給出的,它實際上是通過調用ServletConfig對象的getInitParameter()方法來得到初始化參數的。

public java.util.Enumeration getInitParameterNames()

返回Servlet所有初始化參數的名字的枚舉集合。如果Servlet沒有初始化參數,這個方法將返回一個空的枚舉集合。

注意,這個方法只是為了方便而給出的,它實際上是通過調用ServletConfig對象的getInitParameterNames()方法來得到所有的初始化參數的名字。

public ServletContext getServletContext()

返回Servlet上下文對象的引用,關于ServletContext的使用,請參見第2.5節。

注意,這個方法只是為了方便而給出的,它實際上是通過調用ServletConfig對象的getServletContext()方法來得到的Servlet上下文對象的引用。

2.1.6 HttpServlet

在絕大多數的網絡應用中,都是客戶端(瀏覽器)通過HTTP協議去訪問服務器端的資源,而我們所編寫的Servlet也主要是應用于HTTP協議的請求和響應。為了快速開發應用于HTTP協議的Servlet類,Sun公司在javax.servlet.http包中給我們提供了一個抽象的類HttpServlet,它繼承自GenericServlet類,用于創建適合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()方法的實現。在這個方法中,首先將req和res對象轉換為HttpServletRequest(繼承自ServletRequest接口)和HttpServletResponse(繼承自ServletResponse接口)類型,然后調用第二個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個方法的參數類型及異常拋出類型與HttpServlet類中的第二個重載的service()方法是一致的。當容器接收到一個針對HttpServlet對象的請求時,調用該對象中的方法順序如下:

① 調用公共的(public)service()方法。

② 在公共的service()方法中,首先將參數類型轉換為HttpServletRequest和HttpServletResponse,然后調用保護的(protected)service()方法,將轉換后的HttpServletRequest對象和HttpServletResponse對象作為參數傳遞進去。

③ 在保護的service()方法中,首先調用HttpServletRequest對象的getMethod()方法,獲取HTTP請求方法的名字,然后根據請求方法的類型,調用相應的doXxx ()方法。

因此,我們在編寫HttpServlet的派生類時,通常不需要去覆蓋service()方法,而只需重寫相應的doXXX()方法。

HttpServlet類對TRACE和OPTIONS方法做了適當的實現,因此我們不需要去覆蓋doTrace()和doOptions()方法。而對于其他的5個請求方法,HttpServlet類提供的實現都是返回HTTP錯誤,對于HTTP 1.0的客戶端請求,這些方法返回狀態代碼為400的HTTP錯誤,表示客戶端發送的請求在語法上是錯誤的。而對于HTTP 1.1的客戶端請求,這些方法返回狀態代碼為405的HTTP錯誤,表示對于指定資源的請求方法不被允許。這些方法都是使用javax.servlet.ServletRequest接口中的getProtocol()方法來確定協議的。

HttpServlet雖然是抽象類,但在這個類中沒有抽象的方法,其中所有的方法都是已經實現的。只是在這個類中對客戶請求進行處理的方法,沒有真正的實現,當然也不可能真正實現,因為對客戶請求如何進行處理,需要根據實際的應用來決定。我們在編寫HTTP Servlet的時候,根據應用的需要,重寫其中的對客戶請求進行處理的方法即可。

2.1.7 HttpServletRequest和HttpServletResponse

在javax.servlet.http包中,定義了HttpServletRequest和HttpServletResponse這兩個接口。這兩個接口分別繼承自javax.servlet.ServletRequest和javax.servlet.ServletResponse接口。在HttpServletRequest接口中新增的常用方法如下:

public java.lang.String getContextPath()

返回請求URI中表示請求上下文的部分,上下文路徑是請求URI的開始部分。上下文路徑總是以斜杠(/)開頭,但結束沒有斜杠(/)。在默認(根)上下文中,這個方法返回空字符串""。例如,請求URI為“/sample/test”,調用該方法返回路徑為“/sample”。

public Cookie[] getCookies()

返回客戶端在此次請求中發送的所有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()

返回與客戶端發送的請求URL相聯系的額外的路徑信息。額外的路徑信息是跟在Servlet的路徑之后、查詢字符串之前的路徑,并以斜杠(/)字符開始。例如,假定在web.xml文件中MyServlet類映射的URL是:/myservlet/*,用戶請求的URL是:http://localhost:8080/ch02/myservlet/test,當我們在HttpServletRequest對象上調用getPathInfo()時,該方法將返回/test。如果沒有額外的路徑信息,getPathInfo()方法將返回null。

public java.lang.String getPathTranslated()

將額外的路徑信息轉換為真實的路徑。例如,在上面的例子中假定ch02 Web應用程序位于D:\OpenSource\apache-tomcat-6.0.16\webapps\ch02目錄,當用戶請求http://localhost:8080/ch02/myservlet/test時,在請求對象上調用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

調用getQueryString()方法將返回action=logon。

public java.lang.String getRequestURI()

返回請求URL中從主機名到查詢字符串之間的部分。例如:

public java.lang.StringBuffer getRequestURL()

重新構造客戶端用于發起請求的URL。返回的URL包括了協議、服務器的名字、端口號和服務器的路徑,但是不包括查詢字符串參數。

要注意的是,如果請求使用RequestDispatcher.forward(ServletRequest, ServletResponse)方法被轉發到另一個Servlet中,那么你在這個Servlet中調用getRequestURL(),得到的將是獲取RequestDispatcher對象時使用的URL,而不是原始的請求URL。

public java.lang.String getServletPath()

返回請求URI中調用Servlet的部分。這部分的路徑以斜杠(/)開始,包括了Servlet的名字或者路徑,但是不包括額外的路徑信息和查詢字符串。例如,假定在web.xml文件中MyServlet類映射的URL是:/myservlet/*,用戶請求的URL是:http://localhost:8080/ch02/myservlet/test,當我們在HttpServletRequest對象上調用getServletPath ()時,該方法將返回/myservlet。如果用于處理請求的Servlet與URL樣式“/*”相匹配,那么這個方法將返回空字符串("")。

public HttpSession getSession()

返回和此次請求相關聯的Session,如果沒有給客戶端分配Session,則創建一個新的Session。

public HttpSession getSession(boolean create)

返回和此次請求相關聯的Session,如果沒有給客戶端分配Session,而create參數為true,則創建一個新的Session。如果create參數為false,而此次請求沒有一個有效的HttpSession,則返回null。

在HttpServletResponse接口中,新增的常用方法如下:

public void addCookie(Cookie cookie)

增加一個Cookie到響應中。這個方法可以被多次調用,用于設置多個Cookie。

public void addHeader(java.lang.String name, java.lang.String value)

用給出的name和value,增加一個響應報頭到響應中。

public boolean containsHeader(java.lang.String name)

判斷以name為名字的響應報頭是否已經設置。

public java.lang.String encodeRedirectURL(java.lang.String url)

使用Session ID對用于重定向的url進行編碼,以便用于sendRedirect()方法中。如果該url不需要編碼,則返回未改變的url。(關于這個方法的使用,請參見第5章)

public java.lang.String encodeURL(java.lang.String url)

使用Session ID對指定的url進行編碼。如果該url不需要編碼,則返回未改變的url。(關于這個方法的使用,請參見第5章)

public void sendError(int sc) throws java.io.IOException

使用參數sc表示的狀態代碼發送一個錯誤響應到客戶端,同時清除緩存。如果響應已經被提交,這個方法將拋出IllegalStateException異常。

public void sendError(int sc, java.lang.String msg) throws java.io.IOException

使用指定的狀態代碼發送一個錯誤響應到客戶端。服務器默認會創建一個包含了指定消息的服務器端錯誤頁面作為響應,設置內容類型為“text/html”。如果Web應用程序已經聲明了對應于指定狀態代碼的錯誤頁面,則服務器會將這個頁面發送給客戶端,而不理會參數msg指定的錯誤消息。如果響應已經被提交,這個方法將拋出IllegalStateException異常。

public void sendRedirect(java.lang.String location) throws java.io.IOException

發送一個臨時的重定向響應到客戶端,讓客戶端訪問新的URL。如果指定的位置是相對URL,Servlet容器在發送響應到客戶端之前,必須將相對URL轉換為絕對URL。如果響應已經被提交,這個方法將拋出IllegalStateException異常。

public void setHeader(java.lang.String name, java.lang.String value)

用給出的name和value,設置一個響應報頭。如果這個報頭已經被設置,新的值將覆蓋先前的值。

public void setStatus(int sc)

為響應設置狀態代碼。

此外,在HttpServletResponse接口中,還定義了一組整型的靜態常量,用于表示HTTP錯誤代碼,這些錯誤代碼對應于HTTP/1.1中的錯誤代碼。關于這些錯誤代碼常量,請參看HttpServletResponse接口的API文檔。

要想更好地理解HttpServletRequest和HttpServletResponse的使用,應該結合HTTP協議來看,彼此對照。HTTP協議的介紹參見附錄B。

主站蜘蛛池模板: 海丰县| 拉萨市| 邵武市| 定南县| 洱源县| 车险| 微山县| 内江市| 唐海县| 大安市| 宁晋县| 建宁县| 巫山县| 建始县| 米脂县| 呼玛县| 泌阳县| 平塘县| 中西区| 宜州市| 镇雄县| 阳东县| 社旗县| 冷水江市| 独山县| 贵溪市| 兴和县| 印江| 榆林市| 宁陵县| 阿拉善右旗| 洞头县| 涿州市| 岳阳市| 黄大仙区| 商城县| 长阳| 越西县| 柳河县| 贺兰县| 阳信县|