綜述:Java Servlet是JSP技術的基礎,而且大型的Web應用程序的開發需要Java Servlet和JSP配合才能完成。現在許多Web服務器都支持Servlet,即使不直接支持Servlet的Web服務器,也可以通過附件的應用服務器和模塊來支持Servlet,這得益於Java的跨平臺特性。另外,由於Servlet內部以線程方式提供提供服務,不必對於每個請求都啓動一個進程,並且利用多線程機制可以同時爲多個請求服務,因此Servlet的效率非常高。
但它並不是沒有缺點,和傳統的CGI、ISAPI、NSAPI方式相同,Java Servlet也是利用輸出HTML語句來實現動態網頁的,如果用它來開發整個網站,動態部分和靜態頁面的整合過程將變得無法想象。這就是SUN還要推出JSP的原因。
如何正確理解servlet?
servlet的基本概念
一、Servlet的結構
在具體掌握servlet之前,須對Java語言有所瞭解。我們假設讀者已經具備一定的Java基礎。在Servlet API中最重要的是Servlet接口(interface),所有的servlets都必須實現該接口,途徑有很多:一是直接實現該接口,二是通過擴展類(class)來實現,如 HttpServlet。 這個Servlet接口提供了servlet與客戶端聯繫的方法。Servlet編寫者可以在他們開發 servlet程序時提供更多一些或所有的這樣方法。
當一個servlet接收來自客戶端的調用請求, 它接收兩個對象:一個是ServletRequest,另外一個是ServletResponse。這個ServletRequest類概括從客戶端到服務器之間的聯繫,而 ServletResponse類概括從servlet返回客戶端的聯繫。
ServletRequest接口可以獲取到這樣一些信息,如由客戶端傳送的闡述名稱,客戶端正在使用的協議,產生請求並且接收請求的服務器遠端主機名。它也提供獲取數據流的ServletInputStream, 這些數據是客戶端引用中使用HTTP POST 和 PUT 方法遞交的。一個ServletRequest的子類可以讓servlet獲取更多的協議特性數據。例如:HttpServletRequest 包含獲取 HTTP-specific頭部信息的方法。
ServletResponse接口給出相應客戶端的servlet方法。它允許servlet設置內容長度和迴應的mime類型,並且提供輸出流ServletOutputStream,通過編寫者可以發回相應的數據。ServletResponse子類可以給出更多protocol-specific內容的信息。 例如:HttpServletResponse 包含允許servlet 操作HTTP-specific頭部信息的方法。
上面有關類和接口的描述,構成了一個基本的Servlet框架。HTTP servlets有一些附加的可以提供session-tracking capabilities的方法。servlet編寫者可以利用這些API,在有他人操作時維護servlet與客戶端之間的狀態。
二、Servlet的接口
我們編寫的Servlet ,一般從Javax包的HttpServlet類擴展而來,在HttpServlet中加入了一些附加的方法,這些方法可以被協助處理HTTP 基本請求的HttpServlet類中的方法service自動地調用。這些方法有:
· doGet 用來處理HTTP的GET請求。
這個GET操作僅僅允許客戶從HTTP server上取得(GET)資源。重載此方法的用戶自動允許支持方法HEAD。這個GET操作被認爲是安全的,沒有任何的負面影響,對用戶來說是很可靠的。比如,大多數的正規查詢都沒有副作用。打算改變存儲數據的請求必須用其他的HTTP方法。這些方法也必須是個安全的操作。方法doGet的缺省實現將返回一個HTTP的BAD_REQUEST錯誤。
方法doGet的格式:
protected void doGet(HttpServletResquest request, HttpServletResponse response)
throws ServletException,IOException;
· doPost 用來處理HTTP的POST請求。
這個POST操作包含了在必須通過此servlet執行的請求中的數據。由於它不能立即取得資源,故對於那些涉及到安全性的用戶來說,通過POST請求操作會有一些副作用。
方法doPost的缺省實現將返回一個HTTP的BAD_REQUEST錯誤。當編寫servlet時,爲了支持POST操作必須在子類HttpServlet中實現(implement)此方法。
此方法的格式:
protected void doPost(HttpServletResquest request, HttpServletResponse response)
throws ServletException,IOException;
· doPut用來處理HTTP的PUT請求。
此PUT操作模擬通過FTP發送一個文件。對於那些涉及到安全性的用戶來說,通過PUT請求操作也會有一些副作用。
此方法的格式:
protected void doPut(HttpServletResquest request,HttpServletResponse response)
throws ServletException,IOException;
· doDelete用來處理HTTP的DELETE請求。
此操作允許客戶端請求一個從server移出的URL。對於那些涉及到安全性的用戶來說,通過DELETE請求操作會有一些副作用。
方法doDelete的缺省實現將返回一個HTTP的BAD_REQUEST錯誤。當編寫servlet時,爲了支持DELETE操作,必須在子類HttpServlet中實現(implement)此方法。
此方法的格式:
protected void doDelete (HttpServletResquest request, HttpServletResponse response)
throws ServletException,IOException;
· doHead 用來處理HTTP的HEAD請求。
缺省地,它會在無條件的GET方法執行時運行,但是不返回任何數據到客戶端。只返回包含內容信息的長度的header。由於用到GET操作,此方法應該是很安全的(沒有副作用)也是可重複使用的。此方法的缺省實現(implement)自動地處理了HTTPDE的HEAD操作並且不需要通過一個子類實現(implement)。
此方法的格式:
protected void doHead (HttpServletResquest request,HttpServletResponse response)
throws ServletException,IOException;
· doOptions用來處理HTTP的OPTIONS請求。
此操作自動地決定支持什麼HTTP方法。比如說,如果讀者創建HttpServlet的子類並重載方法doGet,然後方法doOptions會返回下面的header:
Allow:GET,HEAD,TRACE,OPTIONS
一般不需要重載方法doOptions。
此方法的格式:
protected void doOptions (HttpServletResquest request, HttpServletResponse response)
throws ServletException,IOException;
· doTrace用來處理HTTP的TRACE請求。
此方法的缺省實現產生一個包含所有在trace請求中的header的信息的應答(response)。在開發servlet時,多數情況下需要重載此方法。
此方法的格式:
protected void doTrace (HttpServletResquest request, HttpServletResponse response)
throws ServletException,IOException;
在開發以HTTP爲基礎的servlet中,Servlet開發者關心方法doGet和方法doPost即可。
3.Servlet結束時期
Servlets一直運行到他們被服務器卸載。在結束的時候需要收回在init()方法中使用的資源,在Servlet中是通過destory()方法來實現的。
public void destroy()
{
//回收在init()中啓用的資源,如關閉數據庫的連接等。
}
JSP與servlet之間是怎樣的關係?
JSP主要關注於HTML(或者XML)與Java代碼的結合,以及加入其中的JSP標記。如果一個支持JSP的服務器遇到一個JSP頁面,它首先查看該頁面是否被編譯成爲一個servlet。由此可見,JSP被編譯成servlet,即被轉變爲純Java,然後被裝載入服務器執行。當然,這一過程,根據不同的JSP引擎而略有不同。
JSP和servlet在應用上有什麼區別
簡單的說,SUN首先發展出SERVLET,其功能比較強勁,體系設計也很先進,只是,它輸出HTML語句還是採用了老的CGI方式,是一句一句輸出,所以,編寫和修改HTML非常不方便。
後來SUN推出了類似於ASP的嵌套型的JSP,把JSP TAG嵌套到HTML語句中,這樣,就大大簡化和方便了網頁的設計和修改。新型的網絡語言如ASP,PHP都是嵌套型的。
從網絡三層結構的角度看,一個網絡項目最少分三層:data layer,business layer,,presentation layer。當然也可以更復雜。
SERVLET用來寫business layer是很強大的,但是對於寫presentation layer就很不方便。JSP則主要是爲了方便寫presentation layer而設計的。當然也可以寫business layer。寫慣了ASP,PHP,CGI的朋友,經常會不自覺的把presentation layer和business layer混在一起。比如把數據庫處理信息放到JSP中,其實,它應該放在business layer中。
根據SUN自己的推薦,JSP中應該僅僅存放與presentation layer有關的部分,也就是說,只放輸出HTML網頁的部份。而所有的數據計算、數據分析、數據庫聯結處理,統統是屬於business layer,應該放在JAVA BEANS中。通過JSP調用JAVA BEANS,實現兩層的整合。
實際上,微軟前不久推出的DNA技術,簡單說,就是ASP+COM/DCOM技術。與JSP+BEANS完全類似,所有的presentation layer由ASP完成,所有的business layer由COM/DCOM完成。通過調用,實現整合。
爲什麼要採用這些組件技術呢?因爲單純的ASP/JSP語言是非常低效率執行的,如果出現大量用戶點擊,純SCRIPT語言很快就到達了他的功能上限,而組件技術就能大幅度提高功能上限,加快執行速度。
另外一方面,純SCRIPT語言將presentation layer和business layer混在一起,造成修改不方便,並且代碼不能重複利用。如果想修改一個地方,經常會牽涉到十幾頁CODE,採用組件技術就只改組件就可以了。
綜上所述,SERVLET是一個不完善的產品,寫business layer很好,寫presentation layer就很遜色許多了,並且兩層混雜。所以,推出JSP+BAEN,用JSP寫presentation layer,用BAEN寫business layer。SUN自己的意思也是將來用JSP替代SERVLET。
所以,學了JSP,不會用JAVA BEAN並進行整合,等於沒學。
如何調用servlet?
要調用Servlet或Web應用程序,請使用下列任一種方法:由URL調用、在<FORM>標記中調用、在<SERVLET>標記中調用、在ASP文件中調用。
1.由URL調用 Servlet
這裏有兩種用Servlet的URL從瀏覽器中調用該Servlet的方法:
(1)指定 Servlet 名稱:當用 WebSphere應用服務器管理器來將一個Servlet實例添加(註冊)到服務器配置中時,必須指定"Servlet 名稱"參數的值。例如,可以指定將hi作爲HelloWorldServlet的Servlet名稱。要調用該Servlet,需打開http://your.server.name/servlet/hi。也可以指定Servlet和類使用同一名稱(HelloWorldServlet)。在這種情況下,將由http://your.server.name/servlet/ HelloWorldServlet 來調用Servlet的實例。
(2)指定 Servlet 別名:用 WebSphere應用服務器 管理器來配置Servlet別名,該別名是用於調用Servlet的快捷URL。快捷URL中不包括Servlet名稱。
2.在<FORM>標記中指定Servlet
可以在<FORM>標記中調用Servlet。HTM 格式使用戶能在Web頁面(即從瀏覽器)上輸入數據,並向Servlet提交數據。例如:
<FORM METHOD="GET" ACTION="/servlet/myservlet"> <OL> <INPUT TYPE="radio" NAME="broadcast" VALUE="am">AM<BR> <INPUT TYPE="radio" NAME="broadcast" VALUE="fm">FM<BR> </OL> (用於放置文本輸入區域的標記、按鈕和其它的提示符。) </FORM> |
ACTION特性表明了用於調用Servlet的URL。關於METHOD的特性,如果用戶輸入的信息是通過GET方法向Servlet提交的,則 Servlet 必須優先使用doGet()方法。反之,如果用戶輸入的信息是通過POST方法向Servlet提交的,則 Servlet 必須優先使用doPost()方法。使用GET方法時,用戶提供的信息是查詢字符串表示的URL編碼。無需對URL進行編碼,因爲這是由表單完成的。然後URL編碼的查詢字符串被附加到Servlet URL中,則整個URL提交完成。URL編碼的查詢字符串將根據用戶同可視部件之間的交互操作,將用戶所選的值同可視部件的名稱進行配對。例如,考慮前面的HTML代碼段將用於顯示按鈕(標記爲AM和FM),如果用戶選擇FM按鈕,則查詢字符串將包含name=value的配對操作爲broadcast=fm。因爲在這種情況下,Servlet將響應HTTP請求,因此Servlet應基於HttpServlet類。Servlet 應根據提交給它的查詢字符串中的用戶信息使用的 GET 或 POST 方法,而相應地使用 doGet() 或 doPost() 方法。
3.在<SERVLET>標記中指定Servlet
當使用<SERVLET>標記來調用Servlet時,如同使用<FORM>標記一樣,無需創建一個完整的HTML頁面。作爲替代,Servlet的輸出僅是HTML頁面的一部分,且被動態嵌入到原始HTML頁面中的其它靜態文本中。所有這些都發生在服務器上,且發送給用戶的僅是結果HTML頁面。建議在Java服務器頁面(JSP)文件中使用 <SERVLET> 標記。
原始HTML頁面中包含<SERVLET> 和</SERVLET> 標記。Servlet將在這兩個標記中被調用,且Servlet的響應將覆蓋這兩個標記間的所有東西和標記本身。如果用戶的瀏覽器可以看到HTML源文件,則用戶將看不到<SERVLET>和</SERVLET>標記。要在 Domino Go Webserver 上使用該方法,請啓用服務器上的服務器端包括功能。部分啓用過程將會涉及到添加特殊文件類型SHTML。當Web服務器接收到一個擴展名爲SHTML的Web頁面請求時,它將搜索 <SERVLET> 和 </SERVLET> 標記。對於所有支持的Web服務器,WebSphere應用服務器將處理SERVLET標記間的所有信息。下列 HTML 代碼段顯示瞭如何使用該技術。
<SERVLET NAME="myservlet" CODE="myservlet.class" CODEBASE="url" initparm1= "value"> <PARAM NAME="parm1" VALUE="value"> </SERVLET> |
使用NAME和CODE屬性帶來了使用上的靈活性。可以只使用其中一個屬性,也可以同時使用兩個屬性。NAME屬性指定了Servlet的名稱(使用WebSphere應用服務器管理器配置的),或不帶.class擴展名的Servlet類名。CODE屬性指定了Servlet類名。使用WebSphere應用服務器時,建議指定NAME和CODE,或當NAME指定了Servlet名稱時,僅指定NAME。如果僅指定了CODE,則會創建一個NAME=CODE的Servlet實例。裝入的Servlet將假設Servlet名稱與NAME屬性中指定的名稱匹配。然後,其它SHTML文件可以成功地使用NAME屬性來指定Servlet的名稱,並調用已裝入的Servlet。NAME的值可以直接在要調用Servlet的URL中使用。如果NAME和CODE都存在,且NAME指定了一個現有Servlet,則通常使用NAME中指定的Servlet。由於Servlet創建了部分HTML文件,所以當創建Servlet時,將可能會使用HttpServlet的一個子類,並優先使用doGet()方法(因爲GET方法是提供信息給Servlet的缺省方法)。另一個選項是優先使用service()方法。另外,CODEBASE是可選的,它指定了裝入Servlet的遠程系統的URL。請使用WebSphere應用服務器管理器來從JAR文件配置遠程Servlet裝入系統。
在上述的標記示例中,initparm1是初始化參數名,value是該參數的值。可以指定多個"名稱-值"對的集合。利用ServletConfig對象(被傳遞到Servlet的init()方法中)的getInitParameterNames()和getInitParameter()方法來查找參數名和參數值的字符串數組。在示例中,parm1是參數名,並在初始化Servlet後被才被設置某個值。因爲只能通過使用"請求"對象的方法來使用以<PARAM>標記設置的參數,所以服務器必須調用Servlet service()方法,以從用戶處傳遞請求。要獲得有關用戶的請求信息,請使用getParameterNames()、getParameter()和getParameterValues()方法。
初始化參數是持續的。假設一臺客戶機通過調用一個包含某些初始化參數的SHTML文件來調用Servlet。並假設第二臺客戶機通過調用第二個SHTML文件來調用同一個Servlet,且該SHTML中未指定任何初始化參數。那麼第一次調用Servlet時所設置的初始化參數將一直可用,並且通過所有其它SHTML文件而調用的所有後繼Servlet都不會更改該參數。直到Servlet調用了destroy()方法後,才能重新設置初始化參數。例如,如果另一個SHTML文件指定了另一個不同的初始化參數值,雖然已此時已裝入了Servlet,但該值仍將被忽略。
4.在ASP文件中調用Servlet
如果在Microsoft Internet Information Server(IIS)上有遺留的ASP文件,並且無法將ASP文件移植成JSP文件時,可用ASP文件來調用Servlet。在WebSphere應用服務器中的ASP支持包括一個用於嵌入Servlet的ActiveX控制,下面介紹ActiveX控制AspToServlet的方法和屬性。
該方法說明如下:
(1)String ExecServletToString(String servletName);執行ServletName,並將其輸出返回到一個字符串中。
(2)ExecServlet(String servletName);執行ServletName,並將其輸出直接發送至 HTML 頁面。
(3)String VarValue(String varName);獲得一預置變量值(其它格式)。
(4)VarValue(String varName, String newVal);設置變量值。變量佔據的總大小應小於0.5個千字節(Kbyte)。且僅對配置文件使用這些變量。
其屬性如下:
= Boolean WriteHeaders;若該屬性爲真,則Servlet提供的標題被寫入用戶處。缺省值爲假。
= Boolean OnTest;若該屬性爲真,服務器會將消息記錄到生成的HTML頁面中。缺省值爲假。
下列ASP 腳本示例是以Microsoft Visual Basic Scripting(VBScript)書寫的。
<% ' Small sample asp file to show the capabilities of the servlets and the ASP GateWay ... %> <H1> Starting the ASP->Java Servlet demo</H1> <% ' Create a Servlet gateway object and initialize it ... Set Javaasp = Server.CreateObject("AspToServlet.AspToServlet") ' Setting these properties is only for the sake of demo. ' These are the default values ... Javaasp.OnTest = False Javaasp.WriteHeaders = False ' Add several variables ... Javaasp.VarValue("gal") = "lag" Javaasp.VarValue("pico")= "ocip" Javaasp.VarValue("tal") = "lat" Javaasp.VarValue("paz") = "zap" Javaasp.VarValue("variable name with spaces") = "variable value with spaces" %> <BR> Lets check the variables <% Response.Write("variable gal = ") Response.Write(Javaasp.VarValue("gal")) %> <BR> <% Response.Write("variable pico = " & Javaasp.VarValue("pico")) %> <BR> |
如何設置servlet類的路徑?
因爲各個服務器對訪問servlet的策略不盡相同,所以在設置servlet類路徑時應該視情況而定。
對於開發中的servlet,只需確認包含Javax.servlet 的JAR文檔在您的類路徑中,並運用如Javac的普通開發工具。
對於 JSDK:JSDK_HOME/servlet.jar
JSDK_HOME/server.jar
對於 Tomcat:TOMCAT_HOME/lib/servlet.jar
對於運行中的servlet,必須爲servlet引擎設置類路徑,這根據不同的引擎,有不同的配置,如哪些庫和目錄應包括,哪些不應包括。注:對於servlet的動態加載引擎如JRun, Apache Jserv, Tomcat,包含servlet類文件的目錄不應在類路徑中,而應在config文件中配置。否則,servlet可以運行,但不能被動態再加載。
Servlet 2.2 規範認爲以下應被容器自動包括,因此您不必把他們手工添加到類路徑。
· 所有的類應放在 webapp/WEB-INF/classes目錄下
· 所有JAR文件放在webapp/WEB-INF/lib 目錄下
· 對webapps的應用體現在文檔系統中,對已打包進JAR文檔的webapps的應用應放入容器的webapps目錄。(例如,TOMCAT_HOME/webapps/myapp.jar)
另外,由Gene McKenna([email protected])撰寫的"The Complete CLASSPATH Guide for Servlets"詳細敘述瞭如何爲JavaWebServer和Jrun設置類路徑。
如何實現servlet與applet的通信?
這個例子將向讀者展示服務器端程序(Servlet)和小應用程序(Applet)之間是如何完成通信活動的。它由三個文件組成,一個是sendApplet.Java文件,用於實現Applet,一個是receiveservlet.Java,用於實現servlet,還有一個是add-servlet.html,用於調用Applet。
在sendApplet.Java文件中,最重要的要屬init()函數和Send()函數,其中init()函數用來生成整個Applet的用戶操作界面,包括消息文本框、發送按鈕等等。而消息的發送過程則由Send()函數來完成。請仔細閱讀下面的代碼:
private void Send() //建立與Servlet的聯接,並取得Servelt的輸出信息 |
整個Applet的詳細代碼請見sendApplet.Java。
當Applet與Servlet建立連接後,工作就可以交給Servlet了,由它來解析客戶端的請求,獲得參數message的值,然後將適當信息返回給客戶端,並由Applet進行顯示。完成該功能的是receiveservlet.Java中的service()函數:
public void service (HttpServletRequest req,HttpServletResponse res) throws ServletException,IOException { res.setContentType("text/plain"); ServletOutputStream out = res.getOutputStream(); out.print("receive user message:"); out.print(req.getParameter("message")); } |
該Servlet的詳細源代碼請見receiveservlet.Java。
最後一個文件是add-servlet.html,它用來調用Applet:
<html> <head> <title>sendApplet</title> </head> <body> <hr> <applet code=sendApplet width=400 height=300 ></applet> <hr> </body> </html> |
是不是很簡單?
如何應用應用Servlet進行圖象處理?
我們在處理數據時,有時希望能用圖象直觀的表述,在這裏有一個巧方法,能方便快捷的實現一些簡單的圖形(不能稱之圖象),比如條形圖,我們不必去用Java來生成並顯示圖象,(Java生成圖象很慢),我們可以這樣來作,先用作圖工具作一個很小的你需要的圖片,再根據你所處理的數據量來實時的加長它,就可以得到所要表述的圖例。比如我們在數據庫中得到了一組數據,我們從中找出最大的那一個,按比列設定其<img>標籤的長度,其它的數據圖形則可與它相比,得到<img>的長度,這樣,一個簡簡單單的條形圖就出來。但有時一些簡單的圖形已經不能解決我們實際遇到的情況,比如曲線圖就不能用這種方法,這時我們需要生成Java圖象,也許大家都用過applet這樣的程序吧,若訪問量不大,而實時性又很特殊時(比如股票系統),必須這樣用它。但事實上,我們web程序大多有前後臺之分,前臺瀏覽,後臺維護。這樣我們可以在後臺用servlet實時動態定時地生成圖象文件,而前臺只是查看靜態圖片,這比你用applet來動態產生圖象的速度快了不知多少倍,因爲applet來動態產生圖象,有兩個地方很費時,一是數據庫查詢時間,二是applet本身生成圖象就很慢。下面以一個簡單的例子來說明一下怎樣生成並寫入圖象文件,本例注重的是怎樣寫入圖象文件,相信寫過applet的讀者會生成更加漂亮的圖象。
package test; public class Servlet2 extends HttpServlet public void doGet(HttpServletRequest request, HttpServletResponse response) |
如何通過Servlet調用JavaBean輸出結果集
以此我們通過一個例子進行說明,該例演示瞭如何通過Servlet調用JavaBean輸出結果集,並打印的方法,共由兩個文件組成,一個是JavaBean,用於實現對數據庫的訪問,並獲得結果集;另一個是Servlet,主要負責JavaBean的調用,並將結果集發送到客戶端。
在JavaBean中,我們將訪問DB2樣例數據庫(sample)中的STAFF表,至於如何實現對數據庫的訪問,讀者可以參考《JSP與JDBC》一章。此外,讀者可以通過修改部分參數,來實現對其他數據庫、表的訪問,達到舉一反三的效果。
該JavaBean的核心是execute()函數:
public void execute() { try { //裝載JDBC驅動程序 Class.forName("COM.ibm.db2.jdbc.app.DB2Driver").newInstance(); //建立對數據庫的連接 conn = DriverManager.getConnection("jdbc:db2:sample", "db2admin", "db2admin"); stmt = conn.createStatement(); String sql = "SELECT * FROM STAFF WHERE DEPT=20"; //執行查詢語句,返回結果集 ResultSet rs = stmt.executeQuery(sql); setResult(rs); } catch (SQLException e) { } catch (IllegalAccessException e2) { } catch (ClassNotFoundException e3) { } catch (InstantiationException e4) {} } |
JavaBean的具體源代碼請見Tbean.Java。
知道數據是如何獲取之後,下面我們來看一下Servlet是如何來調用上述JavaBean的。
同樣看service()方法即可(詳細源代碼請見Tservlet.Java):
public void service(HttpServletRequest req, HttpServletResponse res) Hello Print ID NAME DEPT JOB YEARS SALARY COMM |
如何用Servlet來中斷涉及的多線程
現在我們已經知道,當服務器要卸載一個Servlet時,它會在所有的service都已經完成後再調用destroy()方法。如果程序的操作運行需要很長時間,destroy()被調用時就可能還有其他線程在運行。Servlet程序員必須保證所有的線程都已經完成。
長時間運行響應客戶端請求的那些Servlet應當保留當前有多少方法在運行的記錄。它的long-running方法應當週期性地輪流詢問以確保它們能夠繼續運行下去。如果Servlet被destroy()方法調用,那麼這個long-running方法必須停止工作或清除。
舉例,變量serviceCounter用來統計有多少service方法在運行,變量shuttingDown顯示這個Servlet是否被destroy。每個變量有它自己的獲取方法:
public ShutdownExample extends HttpServlet { private int serviceCounter = 0; private Boolean shuttingDown; … //serviceCounter protected synchronized void enteringServiceMethod() { serviceCounter++; } protected synchronized void leavingServiceMethod() { serviceCounter--; } protected synchronized int numServices() { return serviceCounter; } //shuttingDown protected setShuttingDown(Boolean flag) { shuttingDown = flag; } protected Boolean isShuttingDown() { return shuttingDown; } 這個service方法每次在它進入時要增加,而在它返回退出時要減少: protected void service(HttpServletRequest req , HttpServletResponse resp) throws ServletException IOException { enteringServiceMethod(); try{ super.service(req , resp); } finally {leavingServiceMethod();} } |
destroy方法應當檢查serviceCounter,如果存在長時間方式運行的話,設置變量shuttingDown。這個變量將會讓那個正在處理請求的線程知道該結束了。destroy方法應當等待這幾個service方法完成,這樣就是一個清楚的關閉過程了。
public void destroy() { //檢查是否有線程在運行,如果有,告訴它們停止 if (numServices() > 0) { setShuttingDown(true); } //等待它們停止 while(numService() > 0) { try{ thisThread.sleep(interval); }catch(InterruptedException e) {} } } long-running方法如必要應當檢查這個變量,並且解釋它們的工作: public void doPost(…) { … for(i = 0; ((i < lotsOfStuffToDo) && !isShuttingDown()); i++) { try{ partOfLongRunningOperation(i); }catch (InterruptedException e) {} } } |
附錄:深入理解servlet
Servlets(Java小服務器程序)是Java中新增加的一個全新功能。一般來說,Servlets是由服務器端調用和執行的任何Java類,瀏覽器端運行的Java程序叫Applet,Web服務器端運行的Java程序叫做Servlet。自從有了Servlet後,Java的電子商務才真正的開始,之後的JSP又是在Servlet上有了更進一步的發展。瞭解Servlet的機制也就掌握到了到JSP的實現原理。
在編寫Servlet的時候不需要關心一個Servlet是如何被裝載到服務器環境中,只需要調用Java Servlet API編程接口就行了。在使用Servlet API的時候,程序員完全可以不必瞭解內部運行方式,服務器頭、Cookies、會話都可以通過Servlet來處理。但當我們需要一些特殊功能的時候,就需要了解它的一些實現機制了。
先從Servlet的生命週期說起。一般情況下,可以歸納爲幾點:
1.裝載Servlets。這項操作一般是動態執行的。有些Server提供了相應的管理功能,可以在啓動的時候就裝載Servlet;
2.Server創建一個Servlet實例;
3.Server調用Servlet的init()方法;
4.一個客戶端的請求到達Server;
5.server創建一個請求對象;
6.Server創建一個響應對象;
7.Server激活Servlet的Service()方法,並傳遞請求和響應對象;
8.service()方法獲得關於請求對象的的信息、處理請求、訪問其他資源、獲得需要的信息;
9.service()方法使用響應對象的方法,將響應傳回Server,最終到達客戶端。Service()方法可能機或其他方法已處理請求,如doGet()或doPost()或程序員自己開發的方法;
10.對於更多的客戶端請求,Server創建新的請求和響應對象,仍然激活此Servlet的service()方法,將這兩個對象作爲參數傳遞給它。如此重複以上的循環,但無需再次調用init()方法。也就是說,Servlet()只初始化一次。
11.當Server不再需要Servlet時(一般是當Server關閉的時候),Server調用Servlets的destroy()方法。
從前面我們還可以更進一步得出以下幾點:
Servlet運行時是多線程的,在開發的時候由Servlet API實現,開發人員不需要做特別的處理。Servlet的init()、destroy()的使用就像是一個C++編寫的結構、析構函數,要注意前後的一致。比如在init()打開了一個數據庫,就要在destroy()中關閉。
對一些底層的處理,如要控制消息響應的方式,我們就要重載server()方法,並重新編寫。
爲便於解釋Servlet的實現機制,圍繞HttpServlet,用下圖描述Javax.Http包中常用到的類之間的關係(除掉了JSP部分),限於篇幅,沒有給出每個接口、類的屬性和方法。通過圖示,我們很容易開發一些較低層次的代碼。
從下圖,非常明顯的可以看出HttpServlet是從GenericServlet類繼承下來的。而事實上,GenericServlet類主要是爲了起到三個接口的聯合(這個詞用得可能不科學):Servlet,ServletConfig,Serializable,以獲得系統的支持;除了init()外幾乎沒有實現方法操作。真正調用系統所支持的各項功能是在HttpServlet類中。
就HttpServlet的service()方法,一般來說,當它接收到一個OPTIONS請求時,會調用doOptions()方法,當接收到一個TRACE請求時調用doTrace()。doOptions()缺省執行方式是自動決定什麼樣的HTTP被選擇並且返回哪個信息。相關源代碼如下:
public void service(ServletRequest req,ServletResponse res) throws ServletException,IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest)req; response = (HttpServletResponse)res; } catch(ClassCastException _ex) { throw new ServletException("non-HTTP request or response"); } service(request, response); } protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if(method.equals("GET")) { long lastModified = getLastModified(req); if(lastModified == -1L) { doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader("If-Modified-Since"); if(ifModifiedSince < (lastModified / 1000L) * 1000L) { maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(304); } } } else if(method.equals("HEAD")) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if(method.equals("POST")) doPost(req, resp); else if(method.equals("PUT")) doPut(req, resp); else if(method.equals("DELETE")) doDelete(req, resp); else if(method.equals("OPTIONS")) doOptions(req, resp); else if(method.equals("TRACE")) { doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object errArgs[] = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501, errMsg); } } protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Method methods[] = getAllDeclaredMethods(getClass()); boolean ALLOW_GET = false; boolean ALLOW_HEAD = false; boolean ALLOW_POST = false; boolean ALLOW_PUT = false; boolean ALLOW_DELETE = false; boolean ALLOW_TRACE = true; boolean ALLOW_OPTIONS = true; for(int i = 0; i < methods.length; i++) { Method m = methods[i]; if(m.getName().equals("doGet")) { ALLOW_GET = true; ALLOW_HEAD = true; } if(m.getName().equals("doPost")) ALLOW_POST = true; if(m.getName().equals("doPut")) ALLOW_PUT = true; if(m.getName().equals("doDelete")) ALLOW_DELETE = true; } String allow = null; if(ALLOW_GET && allow == null) allow = "GET"; if(ALLOW_HEAD) if(allow == null) allow = "HEAD"; else allow = allow + ", HEAD"; if(ALLOW_POST) if(allow == null) allow = "POST"; else allow = allow + ", POST"; if(ALLOW_PUT) if(allow == null) allow = "PUT"; else allow = allow + ", PUT"; if(ALLOW_DELETE) if(allow == null) allow = "DELETE"; else allow = allow + ", DELETE"; if(ALLOW_TRACE) if(allow == null) allow = "TRACE"; else allow = allow + ", TRACE"; if(ALLOW_OPTIONS) if(allow == null) allow = "OPTIONS"; else allow = allow + ", OPTIONS"; resp.setHeader("Allow", allow); } protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String CRLF = "/r/n"; String responseString = "TRACE " + req.getRequestURI() + " " + req.getProtocol(); for(Enumeration reqHeaderEnum = req.getHeaderNames(); reqHeaderEnum.hasMoreElements();) { String headerName = (String)reqHeaderEnum.nextElement(); responseString = responseString + CRLF + headerName + ": " + req.getHeader(headerName); } responseString = responseString + CRLF; int responseLength = responseString.length(); resp.setContentType("message/http"); resp.setContentLength(responseLength); ServletOutputStream out = resp.getOutputStream(); out.print(responseString); out.close(); } |
如果我們改寫了上面的代碼,將可以寫出一些針對性更強的特殊領域中的應用。限於篇幅,這裏將不在舉例。另外,在HttpServlet類(或子類)中,可以對一些系統底層支持的功能進行一些操作,比如Session、Cookie等。其中HttpSession接口在Sun公司提供的包中沒有找到它的類,應該是由Server引擎來實現、管理它的相關功能。