在javax.servlet包中定義了兩個異常類,ServletException和UnavailableException。
ServletException類定義了一個通用的異常,可以被init()、service()和doXXX()方法拋出,這個類提供了下面4個構(gòu)造方法和1個實例方法:
該方法構(gòu)造一個新的Servlet異常。
該方法用指定的的消息構(gòu)造一個新的Servlet異常。這個消息可以被寫入服務(wù)器的日志中,或者顯示給用戶。
在Servlet執(zhí)行時,如果有一個異常阻礙了Servlet的正常操作,那么這個異常就是根原因(root cause)異常。如果需要在一個Servlet異常中包含根原因的異常,可以調(diào)用這個構(gòu)造方法,同時包含一個描述消息。例如:可以在ServletException異常中嵌入一個java.sql.SQLException異常。
該方法同上,只是沒有指定描述消息的參數(shù)。
該方法返回引起這個Servlet異常的異常,也就是返回根原因的異常。
UnavailableException類是ServletException類的子類,該異常被Servlet拋出,用于向Servlet容器指示這個Servlet永久地或者暫時地不可用。這個類提供了下面2個構(gòu)造方法和2個實例方法:
該方法用一個給定的消息構(gòu)造一個新的異常,指示Servlet永久不可用。
該方法用一個給定的消息構(gòu)造一個新的異常,指示Servlet暫時不可用。其中的參數(shù)seconds指明在這個以秒為單位的時間內(nèi),Servlet不可用。如果Servlet不能估計出多長時間后它將恢復(fù)功能,可以傳遞一個負數(shù)或零給seconds參數(shù)。
該方法返回Servlet預(yù)期的暫時不可用的秒數(shù)。如果返回一個負數(shù),表明Servlet永久不可用或者不能估計出Servlet多長時間不可用。
該方法返回一個布爾值,用于指示Servlet是否是永久不可用。返回true,表明Servlet永久不可用;返回false,表明Servlet可用或者暫時不可用。
Servlet運行在Servlet容器中,其生命周期由容器來管理。Servlet的生命周期通過javax.servlet.Servlet接口中的init()、service()和destroy()方法來表示。
Servlet的生命周期包含了下面4個階段:
Servlet容器負責加載和實例化Servlet。當Servlet容器啟動時,或者在容器檢測到需要這個Servlet來響應(yīng)第一個請求時,創(chuàng)建Servlet實例。當Servlet容器啟動后,它必須要知道所需的Servlet類在什么位置,Servlet容器可以從本地文件系統(tǒng)、遠程文件系統(tǒng)或者其他的網(wǎng)絡(luò)服務(wù)中通過類加載器加載Servlet類,成功加載后,容器創(chuàng)建Servlet的實例。因為容器是通過Java的反射API來創(chuàng)建Servlet實例,調(diào)用的是Servlet的默認構(gòu)造方法(即不帶參數(shù)的構(gòu)造方法),所以我們在編寫Servlet類的時候,不應(yīng)該提供帶參數(shù)的構(gòu)造方法。
在Servlet實例化之后,容器將調(diào)用Servlet的init()方法初始化這個對象。初始化的目的是為了讓Servlet對象在處理客戶端請求前完成一些初始化的工作,如建立數(shù)據(jù)庫的連接,獲取配置信息等。對于每一個Servlet實例,init()方法只被調(diào)用一次。在初始化期間,Servlet實例可以使用容器為它準備的ServletConfig對象從Web應(yīng)用程序的配置信息(在web.xml中配置)中獲取初始化的參數(shù)信息。在初始化期間,如果發(fā)生錯誤,Servlet實例可以拋出ServletException異?;蛘遀navailableException異常來通知容器。ServletException異常用于指明一般的初始化失敗,例如沒有找到初始化參數(shù);而UnavailableException異常用于通知容器該Servlet實例不可用。例如,數(shù)據(jù)庫服務(wù)器沒有啟動,數(shù)據(jù)庫連接無法建立,Servlet就可以拋出UnavailableException異常向容器指出它暫時或永久不可用。
Servlet容器調(diào)用Servlet的service()方法對請求進行處理。要注意的是,在service()方法調(diào)用之前,init()方法必須成功執(zhí)行。在service()方法中,Servlet實例通過ServletRequest對象得到客戶端的相關(guān)信息和請求信息,在對請求進行處理后,調(diào)用ServletResponse對象的方法設(shè)置響應(yīng)信息。在service()方法執(zhí)行期間,如果發(fā)生錯誤,Servlet實例可以拋出ServletException異?;蛘遀navailableException異常。如果UnavailableException異常指示了該實例永久不可用,Servlet容器將調(diào)用實例的destroy()方法,釋放該實例。此后對該實例的任何請求,都將收到容器發(fā)送的HTTP 404(請求的資源不可用)響應(yīng)。如果UnavailableException異常指示了該實例暫時不可用,那么在暫時不可用的時間段內(nèi),對該實例的任何請求,都將收到容器發(fā)送的HTTP 503(服務(wù)器暫時忙,不能處理請求)響應(yīng)。
當容器檢測到一個Servlet實例應(yīng)該從服務(wù)中被移除的時候,容器就會調(diào)用實例的destroy()方法,以便讓該實例可以釋放它所使用的資源,保存數(shù)據(jù)到持久存儲設(shè)備中。當需要釋放內(nèi)存或者容器關(guān)閉時,容器就會調(diào)用Servlet實例的destroy()方法。在destroy()方法調(diào)用之后,容器會釋放這個Servlet實例,該實例隨后會被Java的垃圾收集器所回收。如果再次需要這個Servlet處理請求,Servlet容器會創(chuàng)建一個新的Servlet實例。
在整個Servlet的生命周期過程中,創(chuàng)建Servlet實例、調(diào)用實例的init()和destroy()方法都只進行一次,當初始化完成后,Servlet容器會將該實例保存在內(nèi)存中,通過調(diào)用它的service()方法,為接收到的請求服務(wù)。下面給出Servlet整個生命周期過程的UML序列圖,如圖2-16所示。
圖2-16 Servlet在生命周期內(nèi)為請求服務(wù)
運行在Java虛擬機中的每一個Web應(yīng)用程序都有一個與之相關(guān)的Servlet上下文。Java Servlet API提供了一個ServletContext接口用來表示上下文。在這個接口中定義了一組方法,Servlet可以使用這些方法與它的Servlet容器進行通信,例如,得到文件的MIME類型,轉(zhuǎn)發(fā)請求,或者向日志文件中寫入日志消息。
ServletContext對象是Web服務(wù)器中的一個已知路徑的根。對于本章的實例,Servlet上下文被定位于http://localhost:8080/ch02。以/ch02請求路徑(稱為上下文路徑)開始的所有請求被發(fā)送到與此ServletContext關(guān)聯(lián)的Web應(yīng)用程序。
Servlet容器提供商負責提供ServletContext接口的實現(xiàn)。Servlet容器在Web應(yīng)用程序加載時創(chuàng)建ServletContext對象,作為Web應(yīng)用程序的運行時表示,ServletContext對象可以被Web應(yīng)用程序中所有的Servlet所訪問。
一個ServletContext對象表示了一個Web應(yīng)用程序的上下文。Servlet容器在Servlet初始化期間,向其傳遞ServletConfig對象,可以通過ServletConfig對象的getServletContext()方法來得到ServletContext對象。也可以通過GenericServlet類的getServletContext()方法得到ServletContext對象,不過GenericServlet類的getServletContext()也是調(diào)用ServletConfig對象的getServletContext()方法來得到這個對象的。
ServletContext接口定義了下面的這些方法,Servlet容器提供了這個接口的實現(xiàn)。
Ø public java.lang.Object getAttribute(java.lang.String name)
Ø public java.util.Enumeration getAttributeNames()
Ø public void removeAttribute(java.lang.String name)
Ø public void setAttribute(java.lang.String name, java.lang.Object object)
上面4個方法用于讀取、移除和設(shè)置共享屬性,任何一個Servlet都可以設(shè)置某個屬性,而同一個Web應(yīng)用程序的另一個Servlet可以讀取這個屬性,不管這些Servlet是否為同一個客戶進行服務(wù)。
該方法返回服務(wù)器上與指定的URL相對應(yīng)的ServletContext對象。給出的uripath參數(shù)必須以斜杠(/)開始,被解釋為相對于服務(wù)器文檔根的路徑。出于安全方面的考慮,如果調(diào)用該方法訪問一個受限制的ServletContext對象,那么該方法將返回null。
該方法是在Servlet 2.5規(guī)范中新增的,用于返回Web應(yīng)用程序的上下文路徑。上下文路徑總是以斜杠(/)開頭,但結(jié)束沒有斜杠(/)。在默認(根)上下文中,這個方法返回空字符串""。
可以為Servlet上下文定義初始化參數(shù),這些參數(shù)被整個Web應(yīng)用程序所使用??梢栽诓渴鹈枋龇╳eb.xml)中使用<context-param>元素來定義上下文的初始化參數(shù),上面兩個方法用于訪問這些參數(shù)。
上面兩個方法用于返回Servlet容器支持的Java Servlet API的主版本和次版本號。例如,對于遵從Servlet 2.4版本的容器,getMajorVersion()方法返回2,getMinorVersion()方法返回4。
該方法返回指定文件的MIME類型,如果類型是未知的,這個方法將返回null。MIME類型的檢測是根據(jù)Servlet容器的配置,也可以在Web應(yīng)用程序的部署描述符中指定。
該方法返回一個RequestDispatcher對象,作為指定路徑上的資源的封裝??梢允褂肦equestDispatcher對象將一個請求轉(zhuǎn)發(fā)(forward)給其他資源進行處理,或者在響應(yīng)中包含(include)資源。要注意的是,傳入的參數(shù)path必須以斜杠(/)開始,被解釋為相對于當前上下文根(context root)的路徑。
該方法與getRequestDispatcher()方法類似。不同之處在于,該方法接受一個在部署描述符中以<servlet-name>元素給出的Servlet(或JSP頁面)的名字作為參數(shù)。
在一個Web應(yīng)用程序中,資源用相對于上下文路徑的路徑來引用,這個方法可以返回資源在服務(wù)器文件系統(tǒng)上的真實路徑(文件的絕對路徑)。返回的真實路徑的格式應(yīng)該適合于運行這個Servlet容器的計算機和操作系統(tǒng)(包括正確的路徑分隔符)。如果Servlet容器不能夠?qū)⑻摂M路徑轉(zhuǎn)換為真實的路徑,這個方法將會返回null。
該方法返回被映射到指定路徑上的資源的URL。傳入的參數(shù)path必須以斜杠(/)開始,被解釋為相對于當前上下文根(context root)的路徑。這個方法允許Servlet容器從任何來源為Servlet生成一個可用的資源。資源可以是在本地或遠程文件系統(tǒng)上,在數(shù)據(jù)庫中,或者在WAR文件中。如果沒有資源映射到指定的路徑上,該方法將返回null。
該方法與getResource()方法類似,不同之處在于,該方法返回資源的輸入流對象。另外,使用getResourceAsStream()方法,元信息(如內(nèi)容長度和內(nèi)容類型)將丟失,而使用getResource()方法,元信息是可用的。
該方法返回資源的路徑列表,參數(shù)path必須以斜杠(/)開始,指定用于匹配資源的部分路徑。例如,一個Web應(yīng)用程序包含了下列資源:
— /welcome.html
— /catalog/index.html
— /catalog/products.html
— /catalog/offers/books.html
— /catalog/offers/music.html
— /customer/login.jsp
— /WEB-INF/web.xml
— /WEB-INF/classes/com.acme.OrderServlet.class
如果調(diào)用getResourcePaths("/"),將返回[/welcome.html, /catalog/, /customer/, /WEB-INF/]。如果調(diào)用getResourcePaths("/catalog/"),將返回[/catalog/index.html, /catalog/products.html, /catalog/offers/]。
該方法返回運行Servlet的容器的名稱和版本。
該方法返回在部署描述符中使用<display-name>元素指定的對應(yīng)于當前ServletContext的Web應(yīng)用程序的名稱。
ServletContext接口提供了上面兩個記錄日志的方法,第一個方法用于記錄一般的日志,第二個方法用于記錄指定異常的棧跟蹤信息。
有時候,我們可能需要統(tǒng)計Web站點上的一個特定頁面的訪問次數(shù),考慮這樣一個場景,你為了宣傳一個產(chǎn)品,在某個門戶網(wǎng)站花錢做了一個鏈接,你希望知道產(chǎn)品頁面每天的訪問量,借此了解廣告的效果。要完成上述功能,可以使用ServletContext對象來保存訪問的次數(shù)。我們知道一個Web應(yīng)用程序只有一個ServletContext對象,而且該對象可以被Web應(yīng)用程序中的所有Servlet所訪問,因此使用ServletContext對象來保存一些需要在Web應(yīng)用程序中共享的信息是再合適不過了。
要在ServletContext對象中保存共享信息,可以調(diào)用該對象的setAttribute()方法,要獲取共享信息,可以調(diào)用該對象的getAttribute()方法。針對本例,我們可以調(diào)用setAttribute()方法將訪問計數(shù)保存到上下文對象中,新增一次訪問時,調(diào)用getAttribute()方法從上下文對象中取出訪問計數(shù)加1,然后再調(diào)用setAttribute()方法保存回上下文對象中。這個實例的開發(fā)主要有下列步驟。
在%CATALINA_HOME%\webapps\ch02\src目錄下新建CounterServlet.java,代碼如例2-14所示。
例2-14 CounterServlet.java
1. package org.sunxin.ch02.servlet;
2.
3. import java.io.IOException;
4. import java.io.PrintWriter;
5.
6. import javax.servlet.ServletContext;
7. import javax.servlet.ServletException;
8. import javax.servlet.http.HttpServlet;
9. import javax.servlet.http.HttpServletRequest;
10. import javax.servlet.http.HttpServletResponse;
11.
12. public class CounterServlet extends HttpServlet
13. {
14. public void doGet(HttpServletRequest req, HttpServletResponse resp)
15. throws ServletException, IOException
16. {
17. ServletContext context = getServletContext();
18. Integer count = null;
19. synchronized(context)
20. {
21. count = (Integer) context.getAttribute("counter");
22. if (null == count)
23. {
24. count = new Integer(1);
25. }
26. else
27. {
28. count = new Integer(count.intValue() + 1);
29. }
30. context.setAttribute("counter", count);
31. }
32.
33. resp.setContentType("text/html;charset=gb2312");
34. PrintWriter out = resp.getWriter();
35.
36. out.println("<html><head>");
37. out.println("<title>頁面訪問統(tǒng)計</title>");
38. out.println("</head><body>");
39. out.println("該頁面已被訪問了" + "<b>" + count + "</b>" + "次");
40. out.println("</body></html>");
41. out.close();
42. }
43. }
在程序代碼的第17行,調(diào)用getServletContext()方法(從GenericServlet類間接繼承而來)得到Web應(yīng)用程序的上下文對象。為了避免線程安全的問題,我們在第19行使用synchronized關(guān)鍵字對context對象進行同步。第21行,調(diào)用上下文對象的getAttribute()方法獲取counter屬性的值。第21~29行,判斷count是否為null,如果為null,則將它的初始值設(shè)為1。當這個Servlet第一次被訪問的時候,在上下文對象中還沒有保存counter屬性,所以獲取該屬性的值將返回null。如果count不為null,則將count加1。第30行,將count作為counter屬性的值保存到ServletContext對象中。當下一次訪問這個Servlet時,調(diào)用getAttribute()方法取出counter屬性的值不為null,于是執(zhí)行第28行的代碼,將count加1,此時count為2,表明頁面被訪問了兩次。
第39行,輸出count,顯示該頁面的訪問次數(shù)。
打開命令提示符,進入%CATALINA_HOME%\webapps\ch02\src目錄,然后執(zhí)行:
javac -d ..\WEB-INF\classes CounterServlet.java
在WEB-INF\classes\org\sunxin\ch02\servlet目錄中生成類文件CounterServlet.class。
編輯WEB-INF目錄下的web.xml文件,添加對本例中的Servlet的配置,完整的內(nèi)容如例2-15所示。
例2-15 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>
<servlet>
<servlet-name>WelcomeServlet</servlet-name>
<servlet-class>
org.sunxin.ch02.servlet.WelcomeServlet </servlet-class>
<init-param>
<param-name>greeting</param-name>
<param-value>Welcome you</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>WelcomeServlet</servlet-name>
<url-pattern>/welcome</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>OutputInfoServlet</servlet-name>
<servlet-class>
org.sunxin.ch02.servlet.OutputInfoServlet </servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>OutputInfoServlet</servlet-name>
<url-pattern>/info</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>
org.sunxin.ch02.servlet.LoginServlet </servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>CounterServlet</servlet-name>
<servlet-class>
org.sunxin.ch02.servlet.CounterServlet </servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CounterServlet</servlet-name>
<url-pattern>/product.html</url-pattern>
</servlet-mapping>
</web-app>
新增加的內(nèi)容以粗體顯示,請讀者注意,在Servlet映射中,我們?yōu)楸纠腟ervlet指定的URL是/product.html,對用戶來說,以為訪問的是一個靜態(tài)頁面,利用部署描述符,可以向客戶端屏蔽服務(wù)器端的實現(xiàn)細節(jié)。
啟動Tomcat服務(wù)器,打開IE瀏覽器,在地址欄中輸入http://localhost:8080/ch02/product.html,你將看到如圖2-17所示的頁面。
單擊圖2-17中的刷新按鈕,你會看到訪問的次數(shù)變?yōu)?。再打開一個瀏覽器,輸入:http://localhost: 8080/ch02/product.html,你會看到第二個瀏覽器中顯示的訪問次數(shù)是3。交替刷新兩個瀏覽器中的頁面,可以看到訪問次數(shù)也在交替增長,說明利用ServletContext保存屬性,可以在多個客戶端之間共享屬性。但要注意的是,不同的Web應(yīng)用程序具有不同的Servlet上下文,所以在不同的Web應(yīng)用程序之間不能利用ServletContext來共享屬性。另外還需要注意的是,訪問次數(shù)在重啟Tomcat服務(wù)器后,將重新從1開始,為了永久保存訪問次數(shù),可以將這個值保存到文件或數(shù)據(jù)庫中。
考慮生活中的一個場景,110報警中心收到群眾報警電話,根據(jù)報警的內(nèi)容(報警地點、事情緊急程度),將報警請求交由不同的派出所進行處理。在這里,110報警中心充當了一個調(diào)度員的角色,它負責將各種報警請求轉(zhuǎn)發(fā)給實際的處理單位。這種處理模型的好處是:
① 給人們提供了統(tǒng)一的報警方式(撥打110)。
② 另一方面,報警中心可以根據(jù)報案人所處的位置、派出所的地理位置與人員狀況,合理調(diào)度資源,安排就近的派出所及時出警。
③ 報警中心并不處理具體的案件,縮短了對報警請求的響應(yīng)時間。
在Web應(yīng)用中,這種處理模型也得到了廣泛的應(yīng)用(參見第10.2節(jié)),這種調(diào)度員的角色通常由Servlet來充當,我們把這樣的Servlet叫做控制器(Controller)。在控制器中,可以將請求轉(zhuǎn)發(fā)(request dispatching)給另外一個Servlet或者JSP頁面,甚至是靜態(tài)的HTML頁面,然后由它們進行處理并產(chǎn)生對請求的響應(yīng)。要完成請求轉(zhuǎn)發(fā),就要用到j(luò)avax.servlet.RequestDispatcher接口。
RequestDispatcher對象由Servlet容器創(chuàng)建,用于封裝一個由路徑所標識的服務(wù)器資源。利用RequestDispatcher對象,可以把請求轉(zhuǎn)發(fā)給其他的Servlet或JSP頁面。在RequestDispatcher接口中定義了兩種方法。
該方法用于將請求從一個Servlet傳遞給服務(wù)器上的另外的Servlet、JSP頁面或者是HTML文件。在Servlet中,可以對請求做一個初步的處理,然后調(diào)用這個方法,將請求傳遞給其他的資源來輸出響應(yīng)。要注意的是,這個方法必須在響應(yīng)被提交給客戶端之前調(diào)用,否則的話,它將拋出IllegalStateException異常。在forward()方法調(diào)用之后,原先在響應(yīng)緩存中的沒有提交的內(nèi)容將被自動清除。
該方法用于在響應(yīng)中包含其他資源(Servlet、JSP頁面或HTML文件)的內(nèi)容。和forward()方法的區(qū)別在于:利用include()方法將請求轉(zhuǎn)發(fā)給其他的Servlet,被調(diào)用的Servlet對該請求做出的響應(yīng)將并入原先的響應(yīng)對象中,原先的Servlet還可以繼續(xù)輸出響應(yīng)信息。而利用forward()方法將請求轉(zhuǎn)發(fā)給其他的Servlet,將由被調(diào)用的Servlet負責對請求做出響應(yīng),而原先Servlet的執(zhí)行則終止。
有三種方法可以用來得到RequestDispatcher對象。一是利用ServletRequest接口中的getRequestDispatcher()方法:
另外兩種是利用ServletContext接口中的getNamedDispatcher()和getRequestDispatcher()方法:
可以看到ServletRequest接口和ServletContext接口各自提供了一個同名的方法getRequestDispatcher(),那么這兩個方法有什么區(qū)別呢?兩個getRequestDispatcher()方法的參數(shù)都是資源的路徑名,不過ServletContext接口中的getRequestDispatcher()方法的參數(shù)必須以斜杠(/)開始,被解釋為相對于當前上下文根(context root)的路徑。例如:/myservlet是合法的路徑,而../myservlet是不合法的路徑;而ServletRequest接口中的getRequestDispatcher()方法的參數(shù)不但可以是相對于上下文根的路徑,而且可以是相對于當前Servlet的路徑。例如:/myservlet和myservlet都是合法的路徑,如果路徑以斜杠(/)開始,則被解釋為相對于當前上下文根的路徑;如果路徑?jīng)]有以斜杠(/)開始,則被解釋為相對于當前Servlet的路徑。ServletContext接口中的getNamedDispatcher()方法則是以在部署描述符中給出的Servlet(或JSP頁面)的名字作為參數(shù)。
在這個例子中,我們編寫一個PortalServlet,在這個Servlet中,首先判斷訪問用戶是否已經(jīng)登錄,如果沒有登錄,則調(diào)用RequestDispatcher接口的include()方法,將請求轉(zhuǎn)發(fā)給LoginServlet2(為了和第2.2.3節(jié)的LoginServlet區(qū)分),LoginServlet2在響應(yīng)中發(fā)送登錄表單;如果已經(jīng)登錄,則調(diào)用RequestDispatcher接口的forward()方法,將請求轉(zhuǎn)發(fā)給第2.2.1節(jié)的WelcomeServlet,向用戶顯示歡迎信息。實例的開發(fā)主要有下列步驟。
在%CATALINA_HOME%\webapps\ch02\src目錄下新建PortalServlet.java,代碼如例2-16所示。
例2-16 PortalServlet.java
1. package org.sunxin.ch02.servlet;
2.
3. import java.io.IOException;
4. import java.io.PrintWriter;
5.
6. import javax.servlet.RequestDispatcher;
7. import javax.servlet.ServletContext;
8. import javax.servlet.ServletException;
9. import javax.servlet.http.HttpServlet;
10. import javax.servlet.http.HttpServletRequest;
11. import javax.servlet.http.HttpServletResponse;
12.
13. public class PortalServlet extends HttpServlet
14. {
15. public void doGet(HttpServletRequest req, HttpServletResponse resp)
16. throws ServletException,IOException
17. {
18. resp.setContentType("text/html;charset=gb2312");
19.
20. PrintWriter out=resp.getWriter();
21.
22. out.println("<html><head><title>");
23. out.println("登錄頁面");
24. out.println("</title></head><body>");
25.
26. String name=req.getParameter("username");
27. String pwd=req.getParameter("password");
28.
29. if("zhangsan".equals(name) && "1234".equals(pwd))
30. {
31. ServletContext context=getServletContext();
32. RequestDispatcher rd=context.getRequestDispatcher("/welcome");
33. rd.forward(req,resp);
34. }
35. else
36. {
37. RequestDispatcher rd=req.getRequestDispatcher("login2");
38. rd.include(req,resp);
39. }
40. out.println("</body></html>");
41. out.close();
42. }
43. public void doPost(HttpServletRequest req,HttpServletResponse resp)
44. throws ServletException,IOException
45. {
46. doGet(req,resp);
47. }
48. }
為了比較RequestDispatcher的forward()和include()方法的區(qū)別,在doGet()方法的開始和結(jié)尾處分別輸出了一段HTML代碼。第22~24行輸出一段HTML代碼,這段HTML代碼和第40行輸出的HTML代碼組成了完整的HTML文檔。第26~27行,從請求中獲取用戶名和密碼。
第31~33行,如果用戶名和密碼正確,則利用上下文對象的getRequestDispatcher()方法得到RequestDispatcher對象,傳入的路徑參數(shù)必須以斜杠(/)開始,然后利用forward()方法將請求轉(zhuǎn)發(fā)給welcome這個Servlet處理。請讀者注意,在forward()方法調(diào)用之后,我們在第22~24行輸出的HTML代碼將自動被清除,執(zhí)行的控制權(quán)將交給welcome,在doGet()方法中剩余的代碼也不再執(zhí)行。第37~38行,如果用戶沒有登錄或者輸入了錯誤的用戶名或密碼,則利用請求對象的getRequestDispatcher()方法得到RequestDispatcher對象,傳入的路徑參數(shù)沒有以斜杠(/)開始,表示相對于當前Servlet的路徑,然后調(diào)用include()方法將請求轉(zhuǎn)發(fā)給login2這個Servlet處理,當login2對請求處理完畢后,執(zhí)行的控制權(quán)回到PortalServlet,將繼續(xù)執(zhí)行第40~41行的代碼。
在%CATALINA_HOME%\webapps\ch02\src目錄下新建LoginServlet2.java,代碼如例2-17所示。
例2-17 LoginServlet2.java
1. package org.sunxin.ch02.servlet;
2.
3. import java.io.IOException;
4. import java.io.PrintWriter;
5.
6. import javax.servlet.ServletException;
7. import javax.servlet.http.HttpServlet;
8. import javax.servlet.http.HttpServletRequest;
9. import javax.servlet.http.HttpServletResponse;
10.
11. public class LoginServlet2 extends HttpServlet
12. {
13. public void doGet(HttpServletRequest req, HttpServletResponse resp)
14. throws ServletException,IOException
15. {
16.
17. resp.setContentType("text/html;charset=gb2312");
18.
19. PrintWriter out=resp.getWriter();
20.
21. out.println("<form method=post action=portal>");
22.
23. out.println("<table>");
24.
25. out.println("<tr>");
26. out.println("<td>請輸入用戶名</td>");
27. out.println("<td><input type=text name=username></td>");
28. out.println("</tr>");
29.
30. out.println("<tr>");
31. out.println("<td>請輸入密碼</td>");
32. out.println("<td><input type=password name=password></td>");
33. out.println("</tr>");
34.
35. out.println("<tr>");
36. out.println("<td><input type=reset value=重填></td>");
37. out.println("<td><input type=submit value=登錄></td>");
38. out.println("</tr>");
39.
40. out.println("</table>");
41. out.println("</form>");
42. }
43.
44. public void doPost(HttpServletRequest req,HttpServletResponse resp)
45. throws ServletException,IOException
46. {
47. doGet(req,resp);
48. }
49. }
代碼的第21~41行,主要是輸出一個登錄的表單,因為在例2-16的PortalServlet中,已經(jīng)輸出了<html>、<head>、<title>和<body>元素,所以在這里就不需要再輸出這些元素了,這里輸出的表單將嵌入到<body>元素的開始標簽和結(jié)束標簽之間。要注意,在doGet()方法的最后,不要調(diào)用out.close()關(guān)閉輸出流對象,因為一旦關(guān)閉,響應(yīng)將被提交,那么在PortalServlet中調(diào)用include()方法之后的代碼將不再有效。
打開命令提示符,進入%CATALINA_HOME%\webapps\ch02\src目錄,然后執(zhí)行:
javac -d ..\WEB-INF\classes PortalServlet.java
javac -d ..\WEB-INF\classes LoginServlet2.java
在WEB-INF\classes\org\sunxin\ch02\servlet目錄中生成類文件PortalServlet.class和LoginServlet2.class。
編輯WEB-INF目錄下的web.xml文件,添加對本例中的Servlet的配置,完整的內(nèi)容如例2-18所示。
例2-18 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>
<servlet>
<servlet-name>WelcomeServlet</servlet-name>
<servlet-class>
org.sunxin.ch02.servlet.WelcomeServlet </servlet-class>
<init-param>
<param-name>greeting</param-name>
<param-value>Welcome you</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>WelcomeServlet</servlet-name>
<url-pattern>/welcome</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>OutputInfoServlet</servlet-name>
<servlet-class>
org.sunxin.ch02.servlet.OutputInfoServlet </servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>OutputInfoServlet</servlet-name>
<url-pattern>/info</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>
org.sunxin.ch02.servlet.LoginServlet </servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>CounterServlet</servlet-name>
<servlet-class>
org.sunxin.ch02.servlet.CounterServlet </servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CounterServlet</servlet-name>
<url-pattern>/product.html</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>PortalServlet</servlet-name>
<servlet-class>
org.sunxin.ch02.servlet.PortalServlet </servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PortalServlet</servlet-name>
<url-pattern>/portal</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LoginServlet2</servlet-name>
<servlet-class>
org.sunxin.ch02.servlet.LoginServlet2 </servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet2</servlet-name>
<url-pattern>/login2</url-pattern>
</servlet-mapping>
</web-app>
新增加的內(nèi)容以粗體顯示,在本例中用到的第2.2.1節(jié)的WelcomeServlet的配置內(nèi)容以斜體顯示。
啟動Tomcat服務(wù)器,打開IE瀏覽器,在地址欄中輸入http://localhost:8080/ch02/portal,你將看到如圖2-18所示的頁面。
因為第一次訪問時,用戶還沒有登錄,因此PortalServlet會先輸出一部分HTML代碼,然后利用RequestDispatcher對象的include()方法調(diào)用LoginServlet2 對請求進行響應(yīng),LoginServlet2輸出登錄表單后,PortalServlet繼續(xù)輸出剩余的HTML代碼,最后關(guān)閉輸出流對象,提交響應(yīng)。我們可以單擊瀏覽器菜單欄上的【查看】菜單,選擇【源文件】,或者在頁面上單擊鼠標右鍵,在彈出的快捷菜單中選擇【查看源文件】,就可以看到此次響應(yīng)輸出的HTML代碼。
在登錄表單中輸入用戶名zhangsan和密碼1234,單擊“登錄”提交按鈕,你將看到如圖2-19所示的頁面。
圖2-19 登錄成功后,PortalServlet經(jīng)由WelcomeServlet發(fā)回的歡迎信息
當用戶輸入了正確的用戶名和密碼,PortalServlet利用forward()方法將請求轉(zhuǎn)發(fā)給WelcomeServlet進行處理,而WelcomeServlet向用戶發(fā)回了歡迎信息。在PortalServlet中調(diào)用forward()方法之前輸出的沒有提交的HTML代碼被清除,而在調(diào)用forward()方法之后,執(zhí)行的控制權(quán)轉(zhuǎn)到WelcomeServlet,PortalServlet中剩余的代碼也不再有效。為了驗證PortalServlet輸出的HTML代碼確實被清除了,讀者可以查看歡迎頁面的源代碼,就可以證實這一點。
請讀者注意圖2-18和圖2-19中瀏覽器的地址欄部分,可以看到不管對請求進行處理的是哪一個Servlet,地址欄中始終顯示的是:http://localhost:8080/ch02/portal。
HttpServletResponse接口的sendRedirect()方法和RequestDispatcher接口的forward()方法都可以利用另外的資源(Servlet、JSP頁面或HTLM文件)來為客戶端進行服務(wù),但是這兩種方法有著本質(zhì)上的區(qū)別。
下面分別給出了sendRedirectt()方法和forward()方法的工作原理圖,如圖2-20和圖2-21所示。
圖2-20 sendRedirect()方法的工作原理圖
圖2-20的交互過程如下:
① 瀏覽器訪問Servlet1。
② Servlet1想讓Servlet2為客戶端服務(wù)。
③ Servlet1調(diào)用sendRedirect()方法,將客戶端的請求重定向到Servlet2。
④ 瀏覽器訪問Servlet2。
⑤ Servlet2對客戶端的請求做出響應(yīng)。
從圖2-20中的交互過程可以看出,調(diào)用sendRedirect()方法,實際上是告訴瀏覽器Servlet2所在的位置,讓瀏覽器重新訪問Servlet2。調(diào)用sendRedirect()方法,會在響應(yīng)中設(shè)置Location響應(yīng)報頭。要注意的是,這個過程對于用戶來說是透明的,瀏覽器會自動完成新的訪問。從圖2-14瀏覽器的地址欄中,可以看到,顯示的URL是重定向之后的URL。
|
圖2-21 forward()方法的工作原理圖
圖2-21的交互過程如下:
① 瀏覽器訪問Servlet1。
② Servlet1想讓Servlet2對客戶端的請求進行響應(yīng),于是調(diào)用forward()方法,將請求轉(zhuǎn)發(fā)給Servlet2進行處理。
③ Servlet2對請求做出響應(yīng)。
從圖2-21中的交互過程可以看出,調(diào)用forward()方法,對瀏覽器來說是透明的,瀏覽器并不知道為其服務(wù)的Servlet已經(jīng)換成Servlet2了,它只知道發(fā)出了一個請求,獲得了一個響應(yīng)。從圖2-18和圖2-19瀏覽器的地址欄中,可以看到,顯示的URL始終是原始請求的URL。
本章內(nèi)容較多,首先介紹了Servlet API中的主要接口及其實現(xiàn)類,包括了與Servlet實現(xiàn)相關(guān)的Servlet接口、GenericServlet抽象類和HttpServlet抽象類,與請求和響應(yīng)相關(guān)的ServletRequest接口、ServletResponse接口、HttpServletRequest接口和HttpServletResponse接口,與Servlet配置相關(guān)的ServletConfig接口,并通過4個實例幫助讀者更好地理解這些接口與類的作用與用法。
其次,我們介紹了Servlet中的異常類:ServletException類和UnavailableException類,UnavailableException類是ServletException類的子類,在后面的章節(jié)中,我們會通過具體的例子來演示這些異常類的用法。
再次,介紹了Servlet生命周期,Servlet的生命周期包括了加載和實例化、初始化、請求處理、服務(wù)終止4個階段,在編寫Servlet時,要根據(jù)Servlet的生命周期,來安排程序功能的實現(xiàn)位置,例如,資源的分配與初始化可以放到init()方法中,主要的請求處理代碼可以放到相應(yīng)的doXXX()方法中,資源的釋放可以放到destroy()方法中。
然后,我們介紹了Servlet上下文,每一個Web應(yīng)用程序都有一個與之相關(guān)的Servlet上下文。Java Servlet API提供了一個ServletContext接口用來表示上下文,ServletContext對象是Web應(yīng)用程序的運行時表示,ServletContext對象可以被Web應(yīng)用程序中所有的Servlet所訪問,利用ServletContext對象可以在多個客戶端之間、多個Servlet之間(要求在同一個Web應(yīng)用程序下)共享屬性。
最后,我們介紹了請求轉(zhuǎn)發(fā)。利用RequestDispatcher對象,可以把請求轉(zhuǎn)發(fā)給其他的Servlet或JSP頁面。讀者要了解RequestDispatcher接口的forward()方法和include()方法的區(qū)別,掌握HttpServletResponse對象的sendRedirect()方法和RequestDispatcher對象的forward()方法的工作原理。在實際應(yīng)用中,可以根據(jù)具體的情況,合理利用這兩種方法來完成業(yè)務(wù)需求。