Servlet
教程
1、
Servlet
簡單介紹
Servlet是對支持Java的server的通常擴充。它最多見的用途是擴展Webserver,提供很是安全的、可移植的、易於使用的CGI替代品。它是一種動態載入的模塊,爲來自Webserver的請求提供服務。它全然運行在Java虛擬機上。因爲它在server端運行,所以它不依賴於瀏覽器的兼容性。
servlet容器:
負責處理客戶請求、把請求傳送給servlet並把結果返回給客戶。不一樣程序的容器實際實現可能有所變化,但容器與servlet之間的接口是由servlet API定義好的,這個接口定義了servlet容器在servlet上要調用的方法及傳遞給servlet的對象類。
servlet的生命週期:
一、servlet容器建立servlet的一個實例
二、容器調用該實例的init()方法
三、假設容器對該servlet有請求,則調用此實例的service()方法
四、容器在銷燬本實例前調用它的destroy()方法
五、銷燬並標記該實例以供做爲垃圾收集
一旦請求了一個servlet,就沒有辦法阻止容器運行一個完整的生命週期。
容器在servlet首次被調用時建立它的一個實例,並保持該實例在內存中,讓它對所有的請求進行處理。容器可以決定在不論何時把這個實例從內存中移走。在典型的模型中,容器爲每個servlet建立一個單獨的實例,容器並不會每接到一個請求就建立一個新線程,而是使用一個線程池來動態的將線程分配給到來的請求,但是這從servlet的觀點來看,效果和爲每個請求建立一個新線程的效果一樣。
servlet API
servlet接口:
public interface Servlet
它的生命週期由javax.servlet.servlet接口定義。當你在寫servlet的時候必須直接或間接的實現這個接口。通常趨向於間接實現:經過從javax.servlet.GenericServlet或javax.servlet.http.HttpServlet派生。在實現servlet接口時必須實現它的五個方法:
init():
public void init(ServletConfig config) throws ServletException
一旦對servlet實例化後,容器就調用此方法。容器把一個ServletConfig對象傳統給此方法,這樣servlet的實例就可以把與容器相關的配置數據保存起來供之後使用。假設此方法沒有正常結束就會拋出一個ServletException。一旦拋出該異常,servlet就再也不運行,而隨後對它的調用會致使容器對它又一次載入並再次運行此方法。接口規定對不論什麼servlet實例,此方法僅僅能被調用一次,在不論什麼請求傳遞給servlet以前,此方法可以在不拋出異常的狀況下運行完畢。
service():
public void service(ServletRequest req,ServletResponse res) throws ServletException,IOException
僅僅有成功初始化後此方法才幹被調用處理用戶請求。前一個參數提供訪問初始請求數據的方法和字段,後一個提供servlet構造響應的方法。
destroy():
public void destroy()
容器可以在不論何時終止servlet服務。容器調用此方法前必須給service()線程足夠時間來結束運行,所以接口規定當service()正在運行時destroy()不被運行。
getServletConfig():
public ServletConfig getServletConfig()
在servlet初始化時,容器傳遞進來一個ServletConfig對象並保存在servlet實例中,該對象贊成訪問兩項內容:初始化參數和ServletContext對象,前者一般由容器在文件裏指定,贊成在運行時向sevrlet傳遞有關調度信息,後者爲servlet提供有關容器的信息。此方法可以讓servlet在不論何時得到該對象及配置信息。
getServletInfo():
public String getServletInfo()
此方法返回一個String對象,該對象包括servlet的信息,好比開發人員、建立日期、描寫敘述信息等。該方法也可用於容器。
GenericServlet類
Public abstract class GenericServlet implants Servlet,ServletConfig,Serializable
此類提供了servlet接口的基本實現部分,其service()方法被申明爲abstract,所以需要被派生。init(ServletConfig conf)方法把servletConfig對象存儲在一個private transient(私有臨時)實例變量裏,getServletConfig()方法返回指向本對象的指針,假設你重載此方法,將不能使用getServletConfig來得到ServletConfig對象,假設確實想重載,記住要包括對super.config的調用。2.1版的API提供一個重載的沒有參數的init()方法。現在在init(ServletConfig)方法結束時有一個對init()的調用,雖然眼下它是空的。2.1版API裏面,此類實現了ServletConfig接口,這使得開發人員不用得到ServletConfig對象狀況下直接調用ServletConfig的方法,這些方法是:getInitParameter(),getInitParameterNames(),getServletContext。此類還包括兩個寫日誌的方法,它們實際上調用的是ServletContext上的相應方法。log(String msg)方法將servlet的名稱和msg參數寫到容器的日誌中,log(String msg,Throwable cause)除了包括servlet外還包括一個異常。
HttpServlet類
該類擴展了GenericServlet類並對servlet接口提供了與HTTP更相關的實現。
service():
protected void service(HttpServletRequest req,HttpServletResponse res) throws ServletException,IOException
public void service(HttpServletRequest req,HttpServletResponse res)throws ServletException,IOException
該方法做爲HTTP請求的分發器,這種方法在不論何時都不能被重載。當請求到來時,service()方法決定請求的類型(GET,POST,HEAD,OPTIONS,DELETE,PUT,TRACE),並把請求分發給相應的處理方法(doGet(),doPost(),doHead(),doOptions(),doDelete(),doPut(),doTrace())每個do方法具備和第一個service()一樣的形式。爲了響應特定類型的HTTP請求,咱們必須重載相應的do方法。假設servlet收到一個HTTP請求而你沒有重載相應的do方法,它就返回一個說明此方法對本資源不可用的標準HTTP錯誤。
getLatModified():
protected long getLastModified(HttpServletRequest req)
該方法返回以毫秒爲單位的的自GMT時間1970年1月1日0時0分0秒依賴的近期一次改動servlet的時間,缺省是返回一個負數表示時間未知。當處理GET請求時,調用此方法可以知道servlet的近期改動時間,server就可決定是否把結果從緩存中去掉。
HttpServletRequest接口
public interface HttpServletRequest extends ServletRequest
所有實現此接口的對象(好比從servlet容器傳遞的HTTP請求對象)都能讓servlet經過本身的方法訪問所有請求的數據。如下是一些用來獲取表單數據的基本方法。
getParameter()
public String getParameter(String key)
此方法試圖將依據查詢串中的keyword定位相應的參數並返回其值。假設有多個值則返回列表中的第一個值。假設請求信息中沒有指定參數,則返回null。
getParameterValues():
public String[] getParameterValues(String key)
假設一個參數可以返回多個值,比方複選框集合,則可以用此方法得到相應參數的所有值。假設請求信息中沒有指定參數,則返回null。
GetParameterNames():
Public Enumeration getParameterNames()
此方法返回一個Enumeration對象,包括相應請求的所有參數名字列表。
HttpServletResponse接口
public interface HttpServletResponse extends servletResponse
servlet容器提供一個實現該接口的對象並經過service()方法將它傳遞給servlet。經過此對象及其方法,servlet可以改動響應頭並返回結果。
setContentType():
public void setContentType(String type)
在給調用者發回響應前,必須用此方法來設置HTTP響應的MIME類型。可以是不論什麼有效的MIME類型,當給瀏覽器返回HTML是就是」text/html」類型。
getWriter():
public PrintWriter getWriter()throws IOException
此方法將返回PrintWriter對象,把servlet的結果做爲文本返回給調用者。PrintWriter對象本身主動把Java內部的UniCode編碼字符轉換成正確的編碼以使client可以閱讀。
getOutputStream():
public ServletOutputStream getOutputStream() throws IOException
此方法返回ServletOutputStream對象,它是java.io.OutputStream的一個子類。此對象向客戶發送二進制數據。
setHeader():
public void setHeader(String name,String value)
此方法用來設置送回給客戶的HTTP響應頭。有一些快捷的方法用來改變某些常用的響應頭,但有時也需要直接調用此方法。
編譯條件
需要從http://java.sun.com/products/servlet/ 得到一份JSDK的拷貝,並把servlet.jar移動到安裝JDK文件夾下的/jre/lib/ext文件夾下。假設是JDK1.1,則移動到/lib下,並在CLASSPATH中添�servlet.jar的絕對路徑。
運行條件
需要Apache Jserv,Jrun Servlet Exec,Java Web Server,Weblogic,WebSphere,Tomcat,Resin等servletserver端程序。
簡單範例
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public
class HelloWorld
extends HttpServlet {
public
void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<body>");
out.println("<head>");
out.println("<title>Hello World!</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Hello World!</h1>");
out.println("</body>");
out.println("</html>");
}
}
servlet的性能和效率
一個servlet僅被初始化一次而運行屢次,所以極小的低效性也會隨着時間的添加�而產生很是很是大的影響。在代碼中需要考慮String對象的使用,假設產生HTML響應需要用到很是多字符串時,不該該爲每個字符串生成一個String對象,因爲這會產生大量的String和StringBuffer對象,形成大量的對象構造消耗和垃圾收集負擔,解決的辦法是一行一行的把所有需要寫入的直接寫入PrintWriter中,或者建立一個StringBuffer對象,並使用append()方法將文本添�。
及時回送
有時,程序需要花費很是長時間運行,在這種狀況下應該回送給client一些信息,而不是長時間顯示白屏,這可以在運行到必定程度就回送一些東西,可以使用PrintWriter的flush()方法強制將現有的內容回送給瀏覽器。
Servlet會話
因爲Webserver使用的協議HTTP是一種無狀態的協議,所以要維護單個客戶機一系列請求的當前狀態就需要使用其餘的附加手段,在曾經,通常的方法是使用:
l 隱藏的表格字段:在瀏覽器中,這種類型的字段是不可見的,然而,它在請求中被傳送,server端程序可以讀取它的值。它的優勢是實現easy,且大多瀏覽器都支持;缺點是字段必須依照特定的順序創建,client可以經過查看源碼獲得其值,在瀏覽器中單擊「後退」button會丟失加到當前頁中的附加字段,同一時候也限制了用戶動態的生成文檔。
l Cookie:是一些重要的做用片段,由server創建並由客戶機的瀏覽器存放。瀏覽器維護一個它本身的Cookie表,這使得它可以做爲一種會話跟蹤的解決方式。使用Cookie的優勢是它比在URL或表單中儲存數據更直觀。它的缺點是它可以用於在比一次短會話更長時間內跟蹤用戶,甚至可以用來跟蹤某個用戶向站點發送的每個請求,所以有人操心本身的隱私問題而關閉了Cookie,一些老的瀏覽器也不支持cookie。Servlet API提供一個Cookie類支持cookie,使用HttpServletResponse.addCookie()和HttpServletResponse.getCookies()方法添加和讀取cookie。
l URL重寫:改動請求的url,使之包括會話ID。這種方法的缺點是:對於大量的數據,URL會變得很是長而失去控制;在某些環境下,URL的字符串長度有必定的限制;數據保密問題,你可能不想讓旁邊的人或者可以使用同一個計算機的看到你的會話數據。Servlet提供HttpServletRequest類可以得到參數。
Servlet API有本身內置的會話跟蹤支持,使用HttpSession對象既可。它的setAttribute()方法綁定一對名字/值數據,把它存到當前會話中,假設會話中已經存在該名字責替換它,語法爲:public void setAttribute(String name,Object value)。getAttribute()方法讀取存儲在會話中的對象,語法爲:public Object getAttribute(String name)。getAttributeNames()方法返回存儲在會話中的所有名字,語法爲:public String[] getAttributeNames()。最後一個方法是removeAttribute()方法,它從會話中刪除指定的信息,語法爲:public void removeAttribute(String name)。HttpSession對象可以使用HttpServletRequest對象request的getSession(true)方法得到。參數爲true意味着假設不存在該對象則建立一個。
2、
servlet
規範定義的
Servlet
生命週期
servlet有良好的生存期的定義,包括怎樣載入、實例化、初始化、處理client請求以及怎樣被移除。這個生存期由javax.servlet.Servlet接口的init,service和destroy方法表達。
一、載入和實例化
容器負責載入和實例化一個servlet。實例化和載入可以發生在引擎啓動的時候,也可以推遲到容器需要該servlet爲客戶請求服務的時候。
首先容器必須先定位servlet類,在必要的狀況下,容器使用一般的Java類載入工具載入該servlet,多是從本機文件系統,也可以是從遠程文件系統甚至其餘的網絡服務。容器載入servlet類之後,它會實例化該類的一個實例。需要注意的是可能會實例化多個實例,好比一個servlet類因爲有不一樣的初始參數而有多個定義,或者servlet實現SingleThreadModel而致使容器爲之生成一個實例池。
二、初始化
servlet載入並實例化後,容器必須在它可以處理client請求前初始化它。初始化的過程主要是讀取永久的配置信息,昂貴資源(好比JDBC鏈接)以及其餘僅僅需要運行一次的任務。經過調用它的init方法並給它傳遞惟一的一個(每個servlet定義一個)ServletConfig對象完畢這個過程。給它傳遞的這個配置對象贊成servlet訪問容器的配置信息中的名稱-值對(name-value)初始化參數。這個配置對象同一時候給servlet提供了訪問實現了ServletContext接口的詳細對象的方法,該對象描寫敘述了servlet的運行環境。
2.1初始化的錯誤處理
在初始化期間,servlet實例可能經過拋出UnavailableException 或者 ServletException異常代表它不能進行有效服務。假設一個servlet拋出一個這種異常,它將不會被置入有效服務並且應該被容器立刻釋放。在此狀況下destroy方法不會被調用因爲初始化沒有成功完畢。在失敗的實例被釋放後,容器可能在不論何時實例化一個新的實例,對這個規則的惟一例外是假設失敗的servlet拋出的異常是UnavailableException並且該異常指出了最小的無效時間,那麼容器就會至少等待該時間指明的時限纔會又一次試圖建立一個新的實例。
2.二、工具因素
當工具(注:依據筆者的理解,這個工具多是應用server的某些檢查工具,通常是驗證應用的合法性和完整性)載入和內省(introspect)一個web應用時,它可能載入和內省該應用中的類,這個行爲將觸發那些類的靜態初始方法被運行,所以,開發人員不能假定僅僅要當servlet的init方法被調用後它才處於活動容器運行狀態(active container runtime)。做爲一個樣例,這意味着servlet不能在它的靜態(類)初始化方法被調用時試圖創建數據庫鏈接或者鏈接EJB容器。
三、處理請求
在servlet被適當地初始化後,容器就可以使用它去處理請求了。每個請求由ServletRequest類型的對象表明,而servlet使用ServletResponse迴應該請求。這些對象被做爲service方法的參數傳遞給servlet。在HTTP請求的狀況下,容器必須提供表明請求和迴應的HttpServletRequest和HttpServletResponse的詳細實現。需要注意的是容器可能會建立一個servlet實例並將之放入等待服務的狀態,但是這個實例在它的生存期中可能根本沒有處理過不論什麼請求。
3.一、多線程問題
容器可能同一時候將多個client的請求發送給一個實例的service方法,這也就意味着開發人員必須確保編寫的servlet可以處理併發問題。假設開發人員想防止這種缺省的行爲,那麼他可以讓他編寫的servlet實現SingleThreadModel。實現這個類可以保證一次僅僅會有一個線程在運行service方法並且一次性運行完。容器可以經過將請求排隊或者維護一個servlet實例池知足這一點。假設servlet是分佈式應用的一部分,那麼,那麼容器可能在該應用分佈的每個JVM中都維護一個實例池。假設開發人員使用synchronizedkeyword定義service方法(或者是doGet和doPost),容器將排隊處理請求,這是由底層的java運行時系統要求的。咱們強烈推薦開發人員不要同步service方法或者HTTPServlet的諸如doGet和doPost這種服務方法。
3.二、處理請求中的異常
servlet在對請求進行服務的時候有可能拋出ServletException或者UnavailableException異常。ServletException代表在處理請求的過程當中發生了錯誤容器應該使用合適的方法清除該請求。UnavailableException代表servlet不能對請求進行處理,多是臨時的,也多是永久的。假設UnavailableException指明是永久性的,那麼容器必須將servlet從服務中移除,調用它的destroy方法並釋放它的實例。假設指明是臨時的,那麼容器可以選擇在異常信息裏面指明的這個臨時沒法服務的時間段裏面不向它發送不論什麼請求。在這個時間段裏面被被拒絕的請求必須使用SERVICE_UNAVAILABLE (503)返回狀態進行響應並且應該攜帶稍後重試(Retry-After)的響應頭代表不能服務僅僅是臨時的。容器也可以選擇不正確臨時性和永久性的不可用進行區分而所有看成永久性的並移除拋出異常的servlet。
3.3線程安全
開發人員應該注意容器實現的請求和響應對象(注:即容器實現的HttpServletRequest和HttpServletResponese)沒有被保證是線程安全的,這就意味着他們僅僅能在請求處理線程的範圍內被使用,這些對象不能被其餘運行線程所引用,因爲引用的行爲是不肯定的。
四、服務結束
容器沒有被要求將一個載入的servlet保存多長時間,所以一個servlet實例可能僅僅在容器中存活了幾毫秒,固然也多是其餘更長的隨意時間(但是確定會短於容器的生存期)
當容器決定將之移除時(緣由多是保存內存資源或者本身被關閉),那麼它必須贊成servlet釋放它正在使用的不論什麼資源並保存不論什麼永久狀態(這個過程經過調用destroy方法達到)。容器在可以調用destroy方法前,它必須贊成那些正在service方法中運行的線程運行完或者在server定義的一段時間內運行(這個時間段在容器調用destroy以前)。一旦destroy方法被調用,容器就不會再向該實例發送不論什麼請求。假設容器需要再使用該servlet,它必須建立新的實例。destroy方法完畢後,容器必須釋放servlet實例以便它可以被垃圾回收。
3、
serlvet
爲何僅僅需要實現
doGet
和
doPost
Serlvet接口僅僅定義了一個服務方法就是service,而HttpServlet類實現了該方法並且要求調用下列的方法之中的一個:
doGet:處理GET請求
doPost:處理POST請求
doPut:處理PUT請求
doDelete:處理DELETE請求
doHead:處理HEAD請求
doOptions:處理OPTIONS請求
doTrace:處理TRACE請求
一般狀況下,在開發基於HTTP的servlet時,開發人員僅僅需要關心doGet和doPost方法,其餘的方法需要開發人員很是的熟悉HTTP編程,所以這些方法被以爲是高級方法。
而一般狀況下,咱們實現的servlet都是從HttpServlet擴展而來。
doPut和doDelete方法贊成開發人員支持HTTP/1.1的相應特性;
doHead是一個已經實現的方法,它將運行doGet但是僅僅向client返回doGet應該向client返回的頭部的內容;
doOptions方法本身主動的返回servlet所直接支持的HTTP方法信息;
doTrace方法返回TRACE請求中的所有頭部信息。
對於那些僅僅支持HTTP/1.0的容器而言,僅僅有doGet, doHead 和 doPost方法被使用,因爲HTTP/1.0協議未定義PUT, DELETE, OPTIONS,或者TRACE請求。
另外,HttpServlet定義了getLastModified方法以支持有條件的(conditional)get操做。有條件的get操做是指使用GET方式請求資源並且在頭部指定僅僅有在資源內容在指定時間後被改動的狀況下server纔有必要回應請求併發送請求的內容。對於那些實現doGet方法並且在不一樣請求之間內容一樣的servlet而言,它應該實現這種方法以提升網絡資源的利用率。
另外要說起的是,依照規範的要求,servlet容器至少要實現HTTP/1.0協議規範,推薦實現HTTP/1.1規範,在此基礎上可以實現其餘的基於請求迴應模式(based request response model)的協議(好比HTTPS)。
4、
servlet
實例的個數及所以引起的問題
在缺省狀況下,一個容器中僅僅爲每個servlet定義生成一個servlet類實例。在servlet實現SingleThreadModel接口的狀況下,容器可以生成多個實例以應付沉重的請求,也可以將請求排隊發送給同一個實例(對於一個高性能的容器,也多是這兩種方式的結合,因爲實例的個數是有限制的,所以在線程安全方式下一個實例會有多個請求排隊等待服務同一時候容器中多個實例可以對請求進行服務)。對於爲可分佈式(distributable)應用開發的servlet而言,在每個JVM中對每個SERVLET定義都會有一個實例,假設在這種應用中servlet也實現了SingleThreadModel接口,那麼在每個JVM中每個servlet定義也可能有多個實例。
使用SingleThreadModel接口可以保證一個線程一次性運行完給定實例的service方法,需要注意的是這個保證僅僅能應用於servlet實例,那些可以被多個servlet實例訪問的對象(好比HttpSession實例)依舊對多個servlet有效,即便他們實現了SingleThreadModel。
依據規範中的這些說明,咱們在實現本身的serlvet時需要考慮多線程的問題,通常而言,不要在servlet中定義可變的成員,僅僅能定義一些常量(使用final定義,假設沒有使用,應該注意在程序中不該該改動其值),筆者見過一個定義很是差的servlet:
public class SomeHttpServlet extends HttpServlet {
HttpSession session;
...
}
這種servlet在使用中必定會出現故障,所有的用戶都會共用一個session(這樣很是節約系統資源,不是嗎?:)),所以一個用戶請求的信息忽然跑到還有一個用戶的ie窗體豪不奇怪。
並且,即便你的servlet實現了SingleThreadModel接口也不要定義可變的成員,因爲該成員的信息會保留下來,而這對於其餘的用戶而言在絕大部分狀況下是毫無心義的。(你肯定會有意義的狀況例外,好比某種計數)
另外需要說明的是上面說明中都是針對servlet定義而言的,而servlet定義定義不等價servlet類定義,即一個servlet類可能會有多個servlet定義,但是筆者尚未找到「servlet定義」的定義,規範中提到實例化一個servlet時可能會有不一樣的初始參數,但是這個也不一樣於帶參數的多個構造方法。普通狀況下咱們可以以爲一個servlet類相應一個servlet定義。
5、
servlet
會話
HTTP協議是一種無狀態的協議,而對於現在的web應用而言,咱們每每需要記錄從特定client的一系列請求間的聯繫。現在已經有很是多會話跟蹤的技術,但是對於程序猿而言都不是很是方便直接使用。servlet規範定義了一個簡單的HttpSession接口以方便servlet容器進行會話跟蹤而不需要開發人員注意實現的細節。
通常而言,有兩種最常用的會話跟蹤機制,一種就是URL重寫。在client不接受cookie的狀況下可以使用URL重寫進行會話跟蹤。URL重寫包括向URL路徑添加一些容器可以解釋的數據。規範要求會話ID必須編碼在URL路徑中,參數名稱必須是jsessionid,好比:
http://www.myserver.com/catalog/index.html;jsessionid=1234
還有一種就是現在最常用的cookie了,規範要求所有的servlet都必須支持cookie。容器向client發送一個cookie,client在興許的處於同一個會話的請求中向server返回該cookie。會話跟蹤cookie的名字必須是JSESSIONID。
新出現的一種會話功能是SSL會話,SSL(Secure Sockets Layer,安全套接字層)是HTTPS協議使用的一種加密技術,內建了會話跟蹤功能,servlet容器可以很是easy的使用這些數據創建會話跟蹤。(但是HTTPS不是規範要求servlet必須支持的協議)
因爲HTTP是一種基於請求響應的協議,所以會話僅僅有在client添�它之後才被新創建。當會話跟蹤信息被成功的返回給server以指示會話給創建時client纔算添�了一個會話。假設client沒有添�會話,那麼下一次請求不會被以爲是會話的一部分。怎樣client還不知道會話或者client選擇不添�一個會話,那麼會話被以爲是新的。開發人員必須本身設計本身的應用中的會話處理狀態,在什麼地方沒有添�會話,什麼地方不能添�會話以及什麼地方不需要添�會話。
規範要求HttpSession在應用或者servlet上下文級別有效,諸如cookie這種創建會話的底層機制可以在上下文中共享,但是對於那些外露的對象,以及更重要的是對象的那些屬性是不能在上下文中共享的。
對於會話的屬性的綁定而言,不論什麼對象都可以綁定到某個命名屬性。被綁定的屬性對象對於其餘處於一樣ServletContext並且處於同一個會話處理中的其餘servlet也是可見的。
某些對象在被添�會話或者被從會話中移除時要求獲得通知,這種信息可以經過讓該對象實現HttpSessionBindingListener接口獲得。該接口定義了兩個方法用以標記被綁定到會話或者從會話中被移除。
valueBound方法在對象經過getAttribute以前就被調用,而valueUnbound方法在對象已經不能經過getAttribute獲得後才被調用。
因爲HTTP是無狀態協議,所以client再也不活動時沒有什麼明顯的信號,這也就意味着僅僅有一種機制可以用於代表client再也不活動:超時。會話的缺省的時限由servlet容器定義並且可以經過HttpSession的getMaxInactiveInterval獲得,開發人員也可以經過使用setMaxInactiveInterval方法進行設置,這些方法返回的單位是秒,假設時限被設置爲-1,那麼意味着永遠不會超時。
經過調用HttpSession的getLastAccessedTime方法,咱們可以獲得在當前請求以前的訪問時間。當會話中的一個請求被servlet上下文處理時會話就被以爲被訪問了。
另外需要注意的就是一些很是重要的會話的語義問題。
多線程問題:多個請求線程可能會同一時候訪問同一個會話,開發人員有責任以適當的方式同步訪問會話中的資源。
分佈式環境:對於被標記爲可分佈的應用而言,同一會話中的所有請求僅僅能被單一的VM處理。同一時候,放入HttpSession中的所有對象都必須實現Serializable接口,不然容器可能會拋出IllegalArgumentException(在jboss_tomcat下沒有拋出這個異常,但是假設在關閉server時還有未完畢的會話,那麼server在試圖存儲會話時會出現串行化異常,在又一次啓動的時候會試圖回覆會話,也會出現異常)。這個限制意味着開發人員不會遇到非可分佈容器中的那些併發問題。另外容器提供者可以經過將一個會話對象以及它的內容從分佈式系統的一個活動節點移動到系統的其餘不一樣節點的能力來保證可伸縮性。
client的語義:基於cookie或者SSL證書通常是被web瀏覽器控制並且不聯繫到特定瀏覽器窗體的事實,從client應用的所有窗體發送到容器的請求均可能是同一個會話。爲了達到最大的可移植性,開發人員不能總假設特定client的所有窗體的請求都處於同一個會話中。
6、
Bean
和
Servlet
的企業應用
J2EE是一個企業應用程序的開發平臺,包括了對EJB、Servlet、JavaServer Page、JNDI、XML等的支持。在這個平臺上可以開發瘦client的多層體系結構的企業應用程序。
Enterprise JavaBean技術是J2EE的主要基礎。EJB技術對在分佈式的計算環境中運行應用邏輯提供了一個可伸縮的框架結構。J2EE經過將EJB組件結構和其餘的企業技術相結合,攻克了在Java平臺上進行開發和配置服務端應用程序的無縫結合。
要使用J2EE開發您的企業應用,您必需要在您的機器上安裝一個Webserver,還要支持XML。爲了在瀏覽器中運行Java 2的API,還要給您的瀏覽器安裝一個支持Java2的插件。
如下就介紹怎樣用J2EE SDK寫一個包括了HTML頁面,Servlet和Session Bean的一個簡單的瘦client的多層體系結構的企業應用程序。聽起來是否是心動了呢?如下就開始吧。
還要提醒一點的就是:在編程的時候,適當的添加�catch子句是一個很是好編程風格。假設樣例代碼拋出了一個正確的異常,代碼就被 try/catch這種程序結構包圍。Catch子句應該中應該添�處理異常的代碼,千萬不要留成空白。至少,應該添�語句:e.printStackTrace()來在控制檯顯示異常信息。
J2EE SDK是一個J2EE平臺上用於演示、教育等用途的非商業的東東。可以從javasoft的站點上免費下載。很是適合用來學習。假設你沒有出國權限,還可以從國內各高校的FTPserver上去下載,速度比較快,但可能版本號不是最新的。
瘦client的多層體系結構的應用程序的樣例:
本樣例經過一個HTML頁面的輸入來調用一個Servlet,Servlet再用Java的名字文件夾服務接口(JNDI)APIs來尋找一個會話Session Bean,用這個Session Bean來運行一個計算。當Servlet獲得了計算的結果的以後,Servlet把計算結果返回給HTML頁面的用戶。
之因此說這是一個瘦client的應用程序,是因爲Servlet自己並無運行不論什麼的應用邏輯。這個簡單的計算是由一個Session Bean在J2EE的應用server上運行的。客戶沒有參與過程的不論什麼操做,所有的計算都是由Session Bean完畢的。
所謂的多層體系結果其實是由三或者四層組成的。咱們的樣例其實是四層的一個結構。三層的體系結構是在標準的兩層的客戶/server結構基礎上,將一個多線程的應用server加到了非瀏覽器的client和後臺數據庫之間。而四層的體系結構是經過Servlet和JavaServer Pages技術將client的應用程序由瀏覽器和HTML頁面來代替。這個樣例咱們臨時僅僅用當中的三層,在下一個樣例中。咱們再去訪問數據庫。這樣,就擴展到四層了。再之後,咱們會涉及到JavaServer Pages技術和XML技術。
J2EE軟件的安裝:
爲了使咱們的樣例可以運行起來,首先要下載一個Java2 SDK Enterprise Edition(J2EE)的1.2.1的版本號和一個J2SE(Java 2 Standard Edition)的1.2以上的版本號。在Windows 2000系統中,假設咱們把J2EE和J2SE都裝到了C:/J2EE文件夾下。安裝詳細文件夾例如如下:
J2EE:C:/J2EE/j2sdkee1.2.1
J2SE:C:/J2EE/jdk1.2.2
Path和ClassPath的設置:
下載的東西包括了J2EE的應用server、Cloudscape數據庫、使用了加密套接字協議層的Webserver、開發和配置的工具、企業級的Java APIs。其Path和ClassPath的設置例如如下:
Path的設置:在Windows系統中,需要把Path的文件夾包括如下兩個文件夾:
C:/J2EE/j2sdkee1.2.1/bin
C:/J2EE/jdk1.2.2/bin
Classpath的設置:在Windows系統中,需要把Classpath參數包括如下的文件:
C:/J2EE/j2sdkee.1.2.1/lib/j2ee.jar
另外,還要配置環境變量:
J2EE_HOME=C:/J2EE/j2sdkee1.2.1
JAVA_HOME=C:/J2EE/jdk1.2.2
這樣,就可以運行C:/J2EE/j2sdkee1.2.1/bin文件夾如下的批處理命令了。細緻看看裏面的批處理,你會發現很多的東西的。
J2EE應用程序組件:
J2EE程序猿編寫J2EE組件。J2EE組件是一個功能齊全的軟件單元。將其餘的應用程序組件組裝到J2EE的應用程序和接口中。J2EE規範中定義例如如下的應用程序組件:
應用程序客戶組件
Enterprise JavaBean組件
Servlet和JavaServer Pages組件(也叫作Web組件)
Applet
在本樣例中,咱們建立了一個J2EE的應用程序和兩個J2EE的組件:一個Servlet和一個Session Bean。Servlet和HTML文件是捆綁在一個WAR(WEB Archive)文件裏。Session Bean的類和接口捆綁到了一個JAR文件裏。而後再把WAR文件和JAR文件加到J2EE的應用程序,捆綁到一個EAR(Enterprise Archive)文件裏。並驗證測試產品環境的配置。
在這所有的步驟中。實際上運行了很是多的不用的角色的功能。編寫Session Bean和Servlet是開發工做。而建立一個J2EE的應用程序,將J2EE組件組裝到應用程序中是應用程序的組裝工做。實際上,這些工做可以在不一樣的地方由不用的人員來作。
建立一個HTML頁面:
這個頁面名字爲bonus.html。HTML代碼例如如下:
代碼中,讓人感興趣的是用別名來調用BonusServlet.class。因爲在後面提到的應用程序的組裝的時候,將它映射到了這個別名BonusServlet上
<HTML>
<BODY BGCOLOR = "WHITE">
<BLOCKQUOTE>
<H3>Bonus Calculation</H3>
<FORM METHOD="GET" ACTION="BonusAlias">
<P>
Enter social security Number:
<P>
<INPUT TYPE="TEXT" NAME="SOCSEC"></INPUT>
<P>
Enter Multiplier:
<P>
<INPUT TYPE="TEXT" NAME="MULTIPLIER"></INPUT>
<P>
<INPUT TYPE="SUBMIT" VALUE="Submit">
<INPUT TYPE="RESET">
</FORM>
</BLOCKQUOTE>
</BODY>
</HTML>
這個HTML文件有兩個數據域,用戶可以輸入社會保險號和一個乘數。當用戶單擊了Submit按紐。BonusServlet就獲得了終端用戶的數據。而後尋找Session Bean。將用戶數據傳遞給Session Bean。Session Bean計算出獎金,把結果返回給Servlet。Servlet再經過還有一個HTML頁面將獎金結果返回給用戶。
建立Servlet:
樣例假定BonusServlet.java文件是在C:/J2EE/Client-Code文件夾如下。在運行的時候,Servlet代碼運行例如如下操做:
得到用戶數據
查找Session Bean
將用戶數據傳遞給Session Bean
在獲得Session Bean的返回結果之後,建立一個HTML頁面將結果返回給客戶。
Servlet代碼例如如下:
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import javax.naming.*;
import javax.rmi.PortableRemoteObject;
import Beans.*;
public class BonusServlet extends HttpServlet {
CalcHome homecalc;
public void init(ServletConfig config)
throws ServletException{
//Look up home interface
try{
InitialContext ctx = new InitialContext();
Object objref = ctx.lookup("calcs");
homecalc =
(CalcHome)PortableRemoteObject.narrow(
objref,
CalcHome.class);
} catch (Exception NamingException) {
NamingException.printStackTrace();
}
}
public void doGet (HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String socsec = null;
int multiplier = 0;
double calc = 0.0;
PrintWriter out;
response.setContentType("text/html");
String title = "EJB Example";
out = response.getWriter();
out.println("<HTML><HEAD><TITLE>");
out.println(title);
out.println("</TITLE></HEAD><BODY>");
try{
Calc theCalculation;
//Get Multiplier and Social Security Information
String strMult =
request.getParameter("MULTIPLIER");
Integer integerMult = new Integer(strMult);
multiplier = integerMult.intValue();
socsec = request.getParameter("SOCSEC");
//Calculate bonus.10 AUGUST 28, 2000
double bonus = 100.00;
theCalculation = homecalc.create();
calc =
theCalculation.calcBonus(multiplier, bonus);
} catch(Exception CreateException){
CreateException.printStackTrace();
}
//Display Data
out.println("<H1>Bonus Calculation</H1>");
out.println("<P>Soc Sec: " + socsec + "<P>");
out.println("<P>Multiplier: " +
multiplier + "<P>");
out.println("<P>Bonus Amount: " + calc + "<P>");
out.println("</BODY></HTML>");
out.close();
}
public void destroy() {
System.out.println("Destroy");
}
}
在import子句中,javax.servlet包括了Servlet Class的協議。Java.io是系統輸入輸出包。Javax.naming裏面包括了Java名字文件夾服務APIs。Javax.rmi是用來Session Bean的home接口和Remote對象的通訊使用的。
在BonusServlet.init方法中,查找Session Bean的home接口。並且產生它的實例。方法使用了JNDI在組件的組裝中的指定的名字calcs。用它來獲得home接口的reference。而後就把這個reference和home接口類傳遞給PortableRemoteObject.narrow方法。來保證把reference轉化爲CalcHome類型。
DoGet()方法有兩個參數。一個是request對象,還有一個是reponse對象。瀏覽器發送一個request對象給Servlet。而Servlet返回一個response對象給瀏覽器。方法訪問request對象裏面的信息,可以發現是誰在發出的請求、請求的數據在什麼表單裏面、是哪一個HTTP頭被髮送。並使用reponse對象產生一個HTML頁面來響應瀏覽器的請求。
當方法處理請求的時候,假設產生輸入輸出錯誤,就拋出一個IOException異常。假設不能處理請求,就會拋出一個ServletException異常。爲了計算獎金值,doGet()建立了一個home接口,調用它的calcBonus。
建立Session Bean:
Session Bean表明了與客戶的一個短暫的會話。假設服務或者客戶有一方崩潰了。數據就消失了。相反,Entity Bean表明了數據庫中一段持久的數據。假設服務或者客戶又一方崩潰了,底層的服務保證數據能被保存下來。
因爲這個Enterprise Bean僅僅是應BonusServlet的請求,運行了一個簡單的計算。假設發生崩潰,可以又一次初始化計算。這樣,咱們在本樣例中就選擇Session Bean來實現這個計算。
在組裝配置好之後,Servlet組件和Session Bean組件怎樣在一個J2EE應用程序中協同工做。容器是Session Bean和支持Session Bean的底層平臺之間的接口。容器是在配置期間產生的。
本樣例假定CalcBean.java、Calc.java和CalcHome.java文件都放在C:/J2EE/Beans文件夾如下。CalcHome.java文件前面的Package名字 Beans和文件夾Beans的名字應該是同樣的。當這些文件被編譯的時候,是從Beans文件夾中編譯,其名字是包的名字後面加一個斜線在加上類或者接口的名字。
CalcHome.java文件:
package Beans;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface CalcHome extends EJBHome {
Calc create() throws CreateException, RemoteException;
}
BonusServlet並不直接同Session Bean通訊。而是經過產生一個CalcHome的實例。這個Home接口擴展了EJBHome接口。有一個Create()方法,用來在容器中產生一個Session Bean。假設沒法產生Session Bean,將會拋出一個CreateException異常。假設不能與Session Bean的遠程方法通訊,就會拋出一個RemoteException異常。
Calc.java文件:
package Beans;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Calc extends EJBObject {
public double calcBonus(int multiplier,
double bonus)
throws RemoteException;
}
產生一個Home接口之後,J2EE應用程序就建立一個Remote接口和一個Session Bean。Remote接口擴展了EJBObject接口。並且聲明瞭一個calcBonus()方法來計算獎金值。方法需要拋出javax.rmi.RemoteException異常。方法的實現在CalcBean類裏面。
CalcBean.java文件:
package Beans;
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
public class CalcBean implements SessionBean {
public double calcBonus(int multiplier,
double bonus) {
double calc = (multiplier*bonus);
return calc;
}
public void ejbCreate() { }
public void setSessionContext(
SessionContext ctx) { }
public void ejbRemove() { }
public void ejbActivate() { }
public void ejbPassivate() { }
public void ejbLoad() { }
public void ejbStore() { }
}
本Session Bean類實現了SessionBean接口,提供了CalcBonus()方法的行爲。在BonusServlet調用CalcHome的Create()方法之後,依次調用setSessionContext()方法和ejbCreate()方法。
這些空的方法是從SessionBean中來的。由容器負責調用。除非在Bean的建立或者刪除裏面,你需要附加一些你本身的操做。否者,你並不需要提供這些方法的行爲。
7、