在本篇我們主要講兩個方面內容,Servlet生命週期和Servlet請求轉寄。其實在之前我們已經大致介紹過Servlet生命週期的整個過程,只不過沒有系統化提及,而在本篇我們會系統的闡述。
Servlet的生命週期
Servlet運行在Servlet容器中,其生命週期由容器來管理。Servlet的生命週期通過javax.servlet.Servlet介面中的init()、service()和destroy()方法來表示。Servlet的生命週期包含了下面4個階段:
(1)載入和執行個體化
Servlet容器負責載入和執行個體化Servlet。當Servlet容器啟動時,或者在容器檢測到需要這個Servlet來響應第一個請求時,建立Servlet執行個體。當Servlet容器啟動後,它必須要知道所需的Servlet類在什麼位置,Servlet容器可以從本地檔案系統、遠程檔案系統或者其他的網路服務中通過類載入器載入Servlet類,成功載入後,容器建立Servlet的執行個體。因為容器是通過Java的反射API來建立Servlet執行個體,調用的是Servlet的預設構造方法(即不帶參數的構造方法),所以我們在編寫Servlet類的時候,不應該提供帶參數的構造方法。
(2)初始化
在Servlet執行個體化之後,容器將調用Servlet的init()方法初始化這個對象。初始化的目的是為了讓Servlet對象在處理用戶端請求前完成一些初始化的工作,如建立資料庫的串連,擷取配置資訊等。對於每一個Servlet執行個體,init()方法只被調用一次。在初始化期間,Servlet執行個體可以使用容器為它準備的ServletConfig對象從Web應用程式的配置資訊(在web.xml中配置)中擷取初始化的參數資訊。在初始化期間,如果發生錯誤,Servlet執行個體可以拋出ServletException異常或者UnavailableException異常來通知容器。ServletException異常用於指明一般的初始化失敗,例如沒有找到初始化參數;而UnavailableException異常用於通知容器該Servlet執行個體不可用。例如,資料庫伺服器沒有啟動,資料庫連接無法建立,Servlet就可以拋出UnavailableException異常向容器指出它暫時或永久不可用。
(3)請求處理
Servlet容器調用Servlet的service()方法對請求進行處理。要注意的是,在service()方法調用之前,init()方法必須成功執行。在service()方法中,Servlet執行個體通過ServletRequest對象得到用戶端的相關資訊和請求資訊,在對請求進行處理後,調用ServletResponse對象的方法設定響應資訊。在service()方法執行期間,如果發生錯誤,Servlet執行個體可以拋出ServletException異常或者UnavailableException異常。如果UnavailableException異常指示了該執行個體永久不可用,Servlet容器將調用執行個體的destroy()方法,釋放該執行個體。此後對該執行個體的任何請求,都將收到容器發送的HTTP
404(請求的資源不可用)響應。如果UnavailableException異常指示了該執行個體暫時不可用,那麼在暫時停用時間段內,對該執行個體的任何請求,都將收到容器發送的HTTP 503(伺服器暫時忙,不能處理請求)響應。
(4)服務終止
當容器檢測到一個Servlet執行個體應該從服務中被移除的時候,容器就會調用執行個體的destroy()方法,以便讓該執行個體可以釋放它所使用的資源,儲存資料到持久存放裝置中。當需要釋放記憶體或者容器關閉時,容器就會調用Servlet執行個體的destroy()方法。在destroy()方法調用之後,容器會釋放這個Servlet執行個體,該執行個體隨後會被Java的垃圾收集器所回收。如果再次需要這個Servlet處理請求,Servlet容器會建立一個新的Servlet執行個體。
在整個Servlet的生命週期過程中,建立Servlet執行個體、調用執行個體的init()和destroy()方法都只進行一次,當初始化完成後,Servlet容器會將該執行個體儲存在記憶體中,通過調用它的service()方法,為接收到的請求服務。下面給出Servlet整個生命週期過程的UML順序圖表,1所示。
圖1 Servlet生命週期的UML順序圖表
如果需要讓Servlet容器在啟動時即載入Servlet,可以在web.xml檔案中配置<load-on-startup>元素。具體配置方法後文會詳細介紹。
請求轉寄
生活中,110警示中心收到群眾警示電話,根據警示的內容(警示地點、事情緊急程度),將警示請求交由不同的派出所進行處理。在這裡,110警示中心充當了一個調度員的角色,它負責將各種警示請求轉寄給實際的處理單位。這種處理模型的好處是:
1)給人們提供了統一的警示方式(撥打110)。
2)警示中心可以根據報案人所處的位置、派出所的地理位置與人員狀況,合理調度資源,安排就近的派出所及時出警。
3)警示中心並不處理具體的案例,縮短了對警示請求的回應時間。
在Web應用中,這種處理模型也得到了廣泛的應用,這種調度員的角色通常由Servlet來充當,這樣的Servlet叫做控制器(Controller)。在控制器中,可以將請求轉寄(request dispatching)給另外一個Servlet或者JSP頁面,甚至是靜態HTML頁面,然後由它們進行處理併產生對請求的響應。要完成請求轉寄,就要用到javax.servlet.RequestDispatcher介面。
RequestDispatcher介面
RequestDispatcher對象由Servlet容器建立,用於封裝一個由路徑所標識的伺服器資源。利用RequestDispatcher對象,可以把請求轉寄給其他的Servlet或JSP頁面。在RequestDispatcher介面中定義了兩種方法。
public void forward(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOException
該方法用於將請求從一個Servlet傳遞給伺服器上的另外的Servlet、JSP頁面或者是HTML檔案。在Servlet中,可以對請求做一個初步的處理,然後調用這個方法,將請求傳遞給其他的資源來輸出響應。要注意的是,這個方法必須在響應被提交給用戶端之前調用,否則的話,它將拋出IllegalStateException異常。在forward()方法調用之後,原先在響應緩衝中的沒有提交的內容將被自動清除。
public void include(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOException
該方法用於在響應中包含其他資源(Servlet、JSP頁面或HTML檔案)的內容。和forward()方法的區別在於:利用include()方法將請求轉寄給其他的Servlet,被調用的Servlet對該請求做出的響應將併入原先的響應對象中,原先的Servlet還可以繼續輸出響應資訊。而利用forward()方法將請求轉寄給其他的Servlet,將由被調用的Servlet負責對請求做出響應,而原先Servlet的執行則終止。
擷取RequestDispatcher對象
有三種方法可以用來擷取RequestDispatcher對象。一是利用ServletRequest介面中的getRequestDispatcher()方法:
public RequestDispatcher getRequestDispatcher(java.lang.String path)
另外兩種是利用ServletContext介面中的getNamedDispatcher()和getRequestDispatcher()方法:
public RequestDispatcher getRequestDispatcher(java.lang.String path)public RequestDispatcher getNamedDispatcher(java.lang.String name)
可以看到ServletRequest介面和ServletContext介面各自提供了一個同名的方法getRequestDispatcher(),那麼這兩個方法有什麼區別呢?
兩個getRequestDispatcher()方法的參數都是資源的路徑名,不過ServletContext介面中的getRequestDispatcher()方法的參數必須以斜杠(/)開始,被解釋為相對於當前上下文根(context root)的路徑。例如:/myservlet是合法的路徑,而../myservlet是不合法的路徑;而ServletRequest介面中的getRequestDispatcher()方法的參數不但可以是相對於上下文根的路徑,而且可以是相對於當前Servlet的路徑。例如:/myservlet和myservlet都是合法的路徑,如果路徑以斜杠(/)開始,則被解釋為相對於當前上下文根的路徑;如果路徑沒有以斜杠(/)開始,則被解釋為相對於當前Servlet的路徑。ServletContext介面中的getNamedDispatcher()方法則是以在部署描述符中給出的Servlet(或JSP頁面)的名字作為參數。
調用ServletContext對象的getContext()方法可以擷取另一個Web應用程式的內容物件,利用該內容物件調用getRequestDispatcher()方法得到的RequestDispatcher對象,可以將請求轉寄到另一個Web應用程式中的資源。但要注意的是,要跨Web應用程式訪問資源,需要在當前Web應用程式的<context>元素的設定中,指定crossContext屬性的值為true。
一個請求轉寄的執行個體
好了,我們繼續來看一個例子,我們編寫一個PortalServlet,該Servlet首先判斷使用者是否已經登入,如果未登入,則調用RequestDispatcher介面的include()方法,將請求轉寄給LoginServlet,LoginServlet在響應中發送登入表單;如果使用者已經登入,則調用RequestDispatcher介面的forward()方法,將請求轉寄給WelcomeServlet,對使用者顯示響應資訊。和上篇文章一樣,我們還是在MyDemo這個Web項目中編寫程式。下面是PortalServlet的代碼:
package com.shan.web;import java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class PortalServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {response.setContentType("text/html;charset=UTF-8"); PrintWriter out=response.getWriter();out.println("<html><head><title>");out.println("登入頁面");out.println("</title></head><body>");String name=request.getParameter("username");String pwd=request.getParameter("passwd");if("shan".equals(name) && "1234".equals(pwd)) {ServletContext context=getServletContext();RequestDispatcher rd=context.getRequestDispatcher("/welcome.do");rd.forward(request,response);} else {RequestDispatcher rd=request.getRequestDispatcher("login.do");rd.include(request,response);}out.println("</body></html>");out.close();}public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException {doGet(request,response);}}
doGet()方法中,首先判斷使用者名稱和密碼,如果存在,則利用內容物件的getRequestDispatcher()方法得到RequestDispatcher對象,傳入的路徑參數必須以斜杠(/)開始,然後利用forward()方法將請求轉寄給welcome.do這個Servlet處理。(註:在forward()方法調用之後,我們之後輸出的HTML代碼將自動被清除,執行的控制權將交給welcome,在doGet()方法中剩餘的代碼也不再執行。)
如果如果使用者沒有登入或者輸入了錯誤的使用者名稱或密碼,則利用請求對象的getRequestDispatcher()方法得到RequestDispatcher對象,傳入的路徑參數沒有以斜杠(/)開始,表示相對於當前Servlet的路徑,然後調用include()方法將請求轉寄給login.do這個Servlet處理,當login.do對請求處理完畢後,執行的控制權回到PortalServlet,將繼續執行之後的代碼。
LoginServlet代碼如下:
package com.shan.web;import java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class LoginServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {response.setContentType("text/html;charset=gb2312"); PrintWriter out=response.getWriter();out.println("<form method=post action=portal.do>");out.println("<table>");out.println("<tr>");out.println("<td>請輸入使用者名稱:</td>");out.println("<td><input type=text name=username></td>");out.println("</tr>");out.println("<tr>");out.println("<td>請輸入密碼:</td>");out.println("<td><input type=password name=passwd></td>");out.println("</tr>");out.println("<tr>");out.println("<td><input type=reset value=重填></td>");out.println("<td><input type=submit value=登入></td>");out.println("</tr>");out.println("</table>");out.println("</form>");}public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException {doGet(request,response);}}
在PortalServlet中,已經輸出了<html>、<head>、<title>和<body>元素,所以在這裡就不需要再輸出這些元素了,這裡輸出的表單將嵌入到<body>元素的開始標籤和結束標籤之間。要注意,在doGet()方法的最後,不要調用out.close()關閉輸出資料流對象,因為一旦關閉,響應將被提交,那麼在PortalServlet中調用include()方法之後的代碼將不再有效。
我們還需要編寫一個WelcomeServlet程式,代碼如下:
package com.shan.web;import java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class WelcomeServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {response.setContentType("text/html;charset=gb2312"); PrintWriter out=response.getWriter();String username=request.getParameter("username");String welcomeInfo="Hello, "+ username;out.println("<html><head><title>");out.println("Welcome Page");out.println("</title></head>");out.println("<body>");out.println(welcomeInfo);out.println("</body></html>");out.close();}public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException {doGet(request,response);}}
程式很簡單,就是取出username,然後將它輸出。
程式編寫好之後,我們需要編譯器,win+r,輸入cmd,然後切換到工程所在目錄。逐條輸入以下語句:
javac -classpath D:\apache-tomcat-7.0.33\lib\servlet-api.jar -d classes src\com\shan\web\PortalServlet.java
javac -classpath D:\apache-tomcat-7.0.33\lib\servlet-api.jar -d classes src\com\shan\web\LoginServlet.java
javac -classpath D:\apache-tomcat-7.0.33\lib\servlet-api.jar -d classes src\com\shan\web\WelcomeServlet.java
編譯完成後,將三個.class檔案複製到D:\apache-tomcat-7.0.33\webapps\MyDemo目錄下的WEB-INF檔案夾中對應的目錄中去(即classes\com\shan\web檔案夾中)。之後編輯web.xml檔案,修改後如下:
<?xml version='1.0' encoding='utf-8'?><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_3_0.xsd" version="3.0" metadata-complete="true"> <description> My Demo. </description><servlet><servlet-name>selectServlet</servlet-name><servlet-class>com.shan.web.MyDemoServlet</servlet-class></servlet><servlet><servlet-name>countServlet</servlet-name><servlet-class>com.shan.web.CounterServlet</servlet-class></servlet><servlet><servlet-name>PortalServlet</servlet-name><servlet-class>com.shan.web.PortalServlet</servlet-class></servlet><servlet><servlet-name>LoginServlet</servlet-name><servlet-class>com.shan.web.LoginServlet</servlet-class></servlet><servlet><servlet-name>WelcomeServlet</servlet-name><servlet-class>com.shan.web.WelcomeServlet</servlet-class></servlet><servlet-mapping><servlet-name>selectServlet</servlet-name><url-pattern>/SelectColor.do</url-pattern></servlet-mapping><servlet-mapping><servlet-name>countServlet</servlet-name><url-pattern>/count.do</url-pattern></servlet-mapping><servlet-mapping><servlet-name>PortalServlet</servlet-name><url-pattern>/portal.do</url-pattern></servlet-mapping><servlet-mapping><servlet-name>LoginServlet</servlet-name><url-pattern>/login.do</url-pattern></servlet-mapping><servlet-mapping><servlet-name>WelcomeServlet</servlet-name><url-pattern>/welcome.do</url-pattern></servlet-mapping></web-app>
在瀏覽器中輸入http://localhost:8080/MyDemo/portal.do,輸入使用者名稱和密碼(shan和1234),就可以看到歡迎資訊Hello, shan。
sendRedirect()和forward()方法的區別
HttpServletResponse介面的sendRedirect()方法和RequestDispatcher介面的forward()方法都可以利用另外的資源(Servlet、JSP頁面或HTLM檔案)來為用戶端進行服務,但是這兩種方法有著本質上的區別。下面分別給出sendRedirectt()方法和forward()方法的工作原理。
sendRedirect()方法的工作原理/互動過程如下:
1)瀏覽器訪問Servlet1。
2)Servlet1想讓Servlet2為用戶端服務。
3)Servlet1調用sendRedirect()方法,將用戶端的請求重新導向到Servlet2。
4)瀏覽器訪問Servlet2。
5)Servlet2對用戶端的請求做出響應。
從上述互動過程可以看出,調用sendRedirect()方法,實際上是告訴瀏覽器Servlet2所在的位置,讓瀏覽器重新訪問Servlet2。調用sendRedirect()方法,會在響應中設定Location響應前序。要注意的是,這個過程對於使用者來說是透明的,瀏覽器會自動完成新的訪問。瀏覽器網址欄顯示的URL是重新導向之後的URL。
forward()方法的工作原理/互動過程如下:
1)瀏覽器訪問Servlet1。
2)Servlet1想讓Servlet2對用戶端的請求進行響應,於是調用forward()方法,將請求轉寄給Servlet2進行處理。
3)Servlet2對請求做出響應。
從上述互動過程可以看出,調用forward()方法,對瀏覽器來說是透明的,瀏覽器並不知道為其服務的Servlet已經換成Servlet2了,它只知道發出了一個請求,獲得了一個響應。瀏覽器網址欄顯示的URL始終是原始請求的URL。
sendRedirect()方法和forward()方法還有一個區別,那就是sendRedirect()方法不但可以在位於同一主機上的不同Web應用程式之間進行重新導向,而且可以將用戶端重新導向到其他伺服器上的Web應用程式資源。
轉載請註明出處:http://blog.csdn.net/iAm333