<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    javaGrowing

      BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
      92 隨筆 :: 33 文章 :: 49 評論 :: 0 Trackbacks
    一、??????? Servlet 簡介
    Servlet是對支持Java的服務器的一般擴充。它最常見的用途是擴展Web服務器,提供非常安全的、可移植的、易于使用的CGI替代品。它是一種動態加載的模塊,為來自Web服務器的請求提供服務。它完全運行在Java虛擬機上。由于它在服務器端運行,因此它不依賴于瀏覽器的兼容性。
    servlet容器:
    負責處理客戶請求、把請求傳送給servlet并把結果返回給客戶。不同程序的容器實際實現可能有所變化,但容器與servlet之間的接口是由servlet?API定義好的,這個接口定義了servlet容器在servlet上要調用的方法及傳遞給servlet的對象類。
    servlet的生命周期:
    1、servlet容器創建servlet的一個實例
    2、容器調用該實例的init()方法
    3、如果容器對該servlet有請求,則調用此實例的service()方法
    4、容器在銷毀本實例前調用它的destroy()方法
    5、銷毀并標記該實例以供作為垃圾收集
    一旦請求了一個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的最近修改時間,服務器就可決定是否把結果從緩存中去掉。
    HttpServletRequest接口
    public?interface?HttpServletRequest?extends?ServletRequest
    所有實現此接口的對象(例如從servlet容器傳遞的HTTP請求對象)都能讓servlet通過自己的方法訪問所有請求的數據。下面是一些用來獲取表單數據的基本方法。
    getParameter()
    public?String?getParameter(String?key)
    此方法試圖將根據查詢串中的關鍵字定位對應的參數并返回其值。如果有多個值則返回列表中的第一個值。如果請求信息中沒有指定參數,則返回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編碼字符轉換成正確的編碼以使客戶端能夠閱讀。
    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等servlet服務器端程序。
    簡單范例

    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()方法將文本加入。
    及時回送
    有時,程序需要花費很長時間執行,在這種情況下應該回送給客戶端一些信息,而不是長時間顯示白屏,這可以在執行到一定程度就回送一些東西,可以使用PrintWriter的flush()方法強制將現有的內容回送給瀏覽器。

    Servlet會話
    由于Web服務器使用的協議HTTP是一種無狀態的協議,因此要維護單個客戶機一系列請求的當前狀態就需要使用其它的附加手段,在以前,一般的方法是使用:
    l????隱藏的表格字段:在瀏覽器中,這種類型的字段是不可見的,然而,它在請求中被傳送,服務器端程序可以讀取它的值。它的優點是實現容易,且大多瀏覽器都支持;缺點是字段必須按照特定的順序建立,客戶端可以通過查看源代碼得到其值,在瀏覽器中單擊“后退”按鈕會丟失加到當前頁中的附加字段,同時也限制了用戶動態的生成文檔。
    l????Cookie:是一些關鍵的數據片斷,由服務器建立并由客戶機的瀏覽器存放。瀏覽器維護一個它自己的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意味著如果不存在該對象則創建一個。
    ?
    二、??? servlet 規范定義的 Servlet? 生命周期
    servlet有良好的生存期的定義,包括如何加載、實例化、初始化、處理客戶端請求以及如何被移除。這個生存期由javax.servlet.Servlet接口的init,service和destroy方法表達。
    1、加載和實例化
    容器負責加載和實例化一個servlet。實例化和加載可以發生在引擎啟動的時候,也可以推遲到容器需要該servlet為客戶請求服務的時候。
    首先容器必須先定位servlet類,在必要的情況下,容器使用通常的Java類加載工具加載該servlet,可能是從本機文件系統,也可以是從遠程文件系統甚至其它的網絡服務。容器加載servlet類以后,它會實例化該類的一個實例。需要注意的是可能會實例化多個實例,例如一個servlet類因為有不同的初始參數而有多個定義,或者servlet實現SingleThreadModel而導致容器為之生成一個實例池。

    2、初始化
    servlet加載并實例化后,容器必須在它能夠處理客戶端請求前初始化它。初始化的過程主要是讀取永久的配置信息,昂貴資源(例如JDBC連接)以及其它僅僅需要執行一次的任務。通過調用它的init方法并給它傳遞唯一的一個(每個servlet定義一個)ServletConfig對象完成這個過程。給它傳遞的這個配置對象允許servlet訪問容器的配置信息中的名稱-值對(name-value)初始化參數。這個配置對象同時給servlet提供了訪問實現了ServletContext接口的具體對象的方法,該對象描述了servlet的運行環境。
    ????2.1初始化的錯誤處理
    ????在初始化期間,servlet實例可能通過拋出UnavailableException?或者?ServletException異常表明它不能進行有效服務。如果一個servlet拋出一個這樣的異常,它將不會被置入有效服務并且應該被容器立即釋放。在此情況下destroy方法不會被調用因為初始化沒有成功完成。在失敗的實例被釋放后,容器可能在任何時候實例化一個新的實例,對這個規則的唯一例外是如果失敗的servlet拋出的異常是UnavailableException并且該異常指出了最小的無效時間,那么容器就會至少等待該時間指明的時限才會重新試圖創建一個新的實例。
    ????2.2、工具因素
    ????當工具(注:根據筆者的理解,這個工具可能是應用服務器的某些檢查工具,通常是驗證應用的合法性和完整性)加載和內省(introspect)一個web應用時,它可能加載和內省該應用中的類,這個行為將觸發那些類的靜態初始方法被執行,因此,開發者不能假定只要當servlet的init方法被調用后它才處于活動容器運行狀態(active?container?runtime)。作為一個例子,這意味著servlet不能在它的靜態(類)初始化方法被調用時試圖建立數據庫連接或者連接EJB容器。

    3、處理請求
    在servlet被適當地初始化后,容器就可以使用它去處理請求了。每一個請求由ServletRequest類型的對象代表,而servlet使用ServletResponse回應該請求。這些對象被作為service方法的參數傳遞給servlet。在HTTP請求的情況下,容器必須提供代表請求和回應的HttpServletRequest和HttpServletResponse的具體實現。需要注意的是容器可能會創建一個servlet實例并將之放入等待服務的狀態,但是這個實例在它的生存期中可能根本沒有處理過任何請求。
    ????3.1、多線程問題
    ????容器可能同時將多個客戶端的請求發送給一個實例的service方法,這也就意味著開發者必須確保編寫的servlet可以處理并發問題。如果開發者想防止這種缺省的行為,那么他可以讓他編寫的servlet實現SingleThreadModel。實現這個類可以保證一次只會有一個線程在執行service方法并且一次性執行完。容器可以通過將請求排隊或者維護一個servlet實例池滿足這一點。如果servlet是分布式應用的一部分,那么,那么容器可能在該應用分布的每個JVM中都維護一個實例池。如果開發者使用synchronized關鍵字定義service方法(或者是doGet和doPost),容器將排隊處理請求,這是由底層的java運行時系統要求的。我們強烈推薦開發者不要同步service方法或者HTTPServlet的諸如doGet和doPost這樣的服務方法。
    ????3.2、處理請求中的異常
    ????servlet在對請求進行服務的時候有可能拋出ServletException或者UnavailableException異常。ServletException表明在處理請求的過程中發生了錯誤容器應該使用合適的方法清除該請求。UnavailableException表明servlet不能對請求進行處理,可能是暫時的,也可能是永久的。如果UnavailableException指明是永久性的,那么容器必須將servlet從服務中移除,調用它的destroy方法并釋放它的實例。如果指明是暫時的,那么容器可以選擇在異常信息里面指明的這個暫時無法服務的時間段里面不向它發送任何請求。在這個時間段里面被被拒絕的請求必須使用SERVICE_UNAVAILABLE?(503)返回狀態進行響應并且應該攜帶稍后重試(Retry-After)的響應頭表明不能服務只是暫時的。容器也可以選擇不對暫時性和永久性的不可用進行區分而全部當作永久性的并移除拋出異常的servlet。
    ????3.3線程安全
    ????開發者應該注意容器實現的請求和響應對象(注:即容器實現的HttpServletRequest和HttpServletResponese)沒有被保證是線程安全的,這就意味著他們只能在請求處理線程的范圍內被使用,這些對象不能被其它執行線程所引用,因為引用的行為是不確定的。

    4、服務結束
    容器沒有被要求將一個加載的servlet保存多長時間,因此一個servlet實例可能只在容器中存活了幾毫秒,當然也可能是其它更長的任意時間(但是肯定會短于容器的生存期)
    當容器決定將之移除時(原因可能是保存內存資源或者自己被關閉),那么它必須允許servlet釋放它正在使用的任何資源并保存任何永久狀態(這個過程通過調用destroy方法達到)。容器在能夠調用destroy方法前,它必須允許那些正在service方法中執行的線程執行完或者在服務器定義的一段時間內執行(這個時間段在容器調用destroy之前)。一旦destroy方法被調用,容器就不會再向該實例發送任何請求。如果容器需要再使用該servlet,它必須創建新的實例。destroy方法完成后,容器必須釋放servlet實例以便它能夠被垃圾回收。
    ?
    三、??? 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但是僅僅向客戶端返回doGet應該向客戶端返回的頭部的內容;
    doOptions方法自動的返回servlet所直接支持的HTTP方法信息;
    doTrace方法返回TRACE請求中的所有頭部信息。

    對于那些僅僅支持HTTP/1.0的容器而言,只有doGet,?doHead?和?doPost方法被使用,因為HTTP/1.0協議沒有定義PUT,?DELETE,?OPTIONS,或者TRACE請求。

    另外,HttpServlet定義了getLastModified方法以支持有條件的(conditional)get操作。有條件的get操作是指使用GET方式請求資源并且在頭部指定只有在資源內容在指定時間后被修改的情況下服務器才有必要回應請求并發送請求的內容。對于那些實現doGet方法并且在不同請求之間內容相同的servlet而言,它應該實現這個方法以提高網絡資源的利用率。

    另外要提及的是,按照規范的要求,servlet容器至少要實現HTTP/1.0協議規范,推薦實現HTTP/1.1規范,在此基礎上可以實現其它的基于請求回應模式(based?request?response?model)的協議(例如HTTPS)。
    ?
    四、??? 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定義。
    ?
    五、??? servlet 會話
    HTTP協議是一種無狀態的協議,而對于現在的web應用而言,我們往往需要記錄從特定客戶端的一系列請求間的聯系。現在已經有很多會話跟蹤的技術,但是對于程序員而言都不是很方便直接使用。servlet規范定義了一個簡單的HttpSession接口以方便servlet容器進行會話跟蹤而不需要開發者注意實現的細節。

    一般而言,有兩種最常用的會話跟蹤機制,一種就是URL重寫。在客戶端不接受cookie的情況下可以使用URL重寫進行會話跟蹤。URL重寫包括向URL路徑添加一些容器可以解釋的數據。規范要求會話ID必須編碼在URL路徑中,參數名稱必須是jsessionid,例如:
    http://www.myserver.com/catalog/index.html;jsessionid=1234

    另一種就是現在最常用的cookie了,規范要求所有的servlet都必須支持cookie。容器向客戶端發送一個cookie,客戶端在后續的處于同一個會話的請求中向服務器返回該cookie。會話跟蹤cookie的名字必須是JSESSIONID。

    新出現的一種會話功能是SSL會話,SSL(Secure?Sockets?Layer,安全套接字層)是HTTPS協議使用的一種加密技術,內建了會話跟蹤功能,servlet容器可以非常容易的使用這些數據建立會話跟蹤。(但是HTTPS不是規范要求servlet必須支持的協議)?

    因為HTTP是一種基于請求響應的協議,因此會話只有在客戶端加入它以后才被新建立。當會話跟蹤信息被成功的返回給服務器以指示會話給建立時客戶端才算加入了一個會話。如果客戶端沒有加入會話,那么下一次請求不會被認為是會話的一部分。如何客戶端還不知道會話或者客戶端選擇不加入一個會話,那么會話被認為是新的。開發者必須自己設計自己的應用中的會話處理狀態,在什么地方沒有加入會話,什么地方不能加入會話以及什么地方不需要加入會話。
    規范要求HttpSession在應用或者servlet上下文級別有效,諸如cookie這樣的建立會話的底層機制可以在上下文中共享,但是對于那些外露的對象,以及更重要的是對象的那些屬性是不能在上下文中共享的。

    對于會話的屬性的綁定而言,任何對象都可以綁定到某個命名屬性。被綁定的屬性對象對于其它處于相同ServletContext并且處于同一個會話處理中的其它servlet也是可見的。
    某些對象在被加入會話或者被從會話中移除時要求得到通知,這樣的信息可以通過讓該對象實現HttpSessionBindingListener接口得到。該接口定義了兩個方法用以標記被綁定到會話或者從會話中被移除。
    valueBound方法在對象通過getAttribute之前就被調用,而valueUnbound方法在對象已經不能通過getAttribute得到后才被調用。

    由于HTTP是無狀態協議,因此客戶端不再活動時沒有什么明顯的信號,這也就意味著只有一種機制可以用于表明客戶端不再活動:超時。會話的缺省的時限由servlet容器定義并且可以通過HttpSession的getMaxInactiveInterval得到,開發者也可以通過使用setMaxInactiveInterval方法進行設置,這些方法返回的單位是秒,如果時限被設置為-1,那么意味著永遠不會超時。

    通過調用HttpSession的getLastAccessedTime方法,我們可以得到在當前請求之前的訪問時間。當會話中的一個請求被servlet上下文處理時會話就被認為被訪問了。

    另外需要注意的就是一些很重要的會話的語義問題。
    多線程問題:多個請求線程可能會同時訪問同一個會話,開發者有責任以適當的方式同步訪問會話中的資源。
    分布式環境:對于被標記為可分布的應用而言,同一會話中的所有請求只能被單一的VM處理。同時,放入HttpSession中的所有對象都必須實現Serializable接口,否則容器可能會拋出IllegalArgumentException(在jboss_tomcat下沒有拋出這個異常,但是如果在關閉服務器時還有未完成的會話,那么服務器在試圖存儲會話時會出現串行化異常,在重新啟動的時候會試圖回復會話,也會出現異常)。這個限制意味著開發者不會遇到非可分布容器中的那些并發問題。另外容器提供者可以通過將一個會話對象以及它的內容從分布式系統的一個活動節點移動到系統的其它不同節點的能力來保證可伸縮性。
    客戶端的語義:基于cookie或者SSL證書通常是被web瀏覽器控制并且不聯系到特定瀏覽器窗口的事實,從客戶端應用的所有窗口發送到容器的請求都可能是同一個會話。為了達到最大的可移植性,開發者不能總假設特定客戶端的所有窗口的請求都處于同一個會話中。
    六、??? Bean Servlet 的企業應用
    J2EE是一個企業應用程序的開發平臺,包括了對EJB、Servlet、JavaServer?Page、JNDI、XML等的支持。在這個平臺上可以開發瘦客戶端的多層體系結構的企業應用程序。

      Enterprise?JavaBean技術是J2EE的主要基礎。EJB技術對在分布式的計算環境中執行應用邏輯提供了一個可伸縮的框架結構。J2EE通過將EJB組件結構和其它的企業技術相結合,解決了在Java平臺上進行開發和配置服務端應用程序的無縫結合。

      要使用J2EE開發您的企業應用,您必須要在您的機器上安裝一個Web服務器,還要支持XML。為了在瀏覽器中運行Java?2的API,還要給您的瀏覽器安裝一個支持Java2的插件。

      下面就介紹怎樣用J2EE?SDK寫一個包括了HTML頁面,Servlet和Session?Bean的一個簡單的瘦客戶端的多層體系結構的企業應用程序。聽起來是不是心動了呢?下面就開始吧。

    還要提醒一點的就是:在編程的時候,適當的增加catch子句是一個很好編程風格。如果例子代碼拋出了一個正確的異常,代碼就被?try/catch這樣的程序結構包圍。Catch子句應該中應該加入處理異常的代碼,千萬不要留成空白。至少,應該加入語句:e.printStackTrace()來在控制臺顯示異常信息。

      J2EE?SDK是一個J2EE平臺上用于演示、教育等用途的非商業的東東。可以從javasoft的網站上免費下載。很適合用來學習。如果你沒有出國權限,還可以從國內各高校的FTP服務器上去下載,速度比較快,但可能版本不是最新的。


    瘦客戶端的多層體系結構的應用程序的例子:

      本例子通過一個HTML頁面的輸入來調用一個Servlet,Servlet再用Java的名字目錄服務接口(JNDI)APIs來尋找一個會話Session?Bean,用這個Session?Bean來執行一個計算。當Servlet得到了計算的結果的之后,Servlet把計算結果返回給HTML頁面的用戶。

      之所以說這是一個瘦客戶端的應用程序,是因為Servlet本身并沒有執行任何的應用邏輯。這個簡單的計算是由一個Session?Bean在J2EE的應用服務器上執行的。客戶沒有參與過程的任何操作,所有的計算都是由Session?Bean完成的。

      所謂的多層體系結果實際上是由三或者四層組成的。我們的例子實際上是四層的一個結構。三層的體系結構是在標準的兩層的客戶/服務器結構基礎上,將一個多線程的應用服務器加到了非瀏覽器的客戶端和后臺數據庫之間。而四層的體系結構是通過Servlet和JavaServer?Pages技術將客戶端的應用程序由瀏覽器和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的應用服務器、Cloudscape數據庫、使用了加密套接字協議層的Web服務器、開發和配置的工具、企業級的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的創建或者刪除里面,你需要附加一些你自己的操作。否者,你并不需要提供這些方法的行為。
    七、
    posted on 2007-06-20 10:27 javaGrowing 閱讀(851) 評論(0)  編輯  收藏 所屬分類: java學習
    主站蜘蛛池模板: 久久久亚洲精华液精华液精华液| 亚洲国产精品成人精品软件| 亚洲av日韩专区在线观看| 99久久99这里只有免费费精品| 91亚洲精品第一综合不卡播放| 国产免费无码AV片在线观看不卡 | baoyu122.永久免费视频| 国产成人精品曰本亚洲79ren| 五月天国产成人AV免费观看| 亚洲男人在线无码视频| 好湿好大好紧好爽免费视频| 久久精品国产亚洲麻豆| 久久免费区一区二区三波多野| 亚洲AV日韩AV永久无码久久 | 亚洲色图在线观看| 91手机看片国产永久免费| 亚洲永久在线观看| 性做久久久久免费观看| 日韩在线观看免费| 亚洲av鲁丝一区二区三区| **aaaaa毛片免费| 亚洲色在线无码国产精品不卡| 在线观着免费观看国产黄| 免费的黄色网页在线免费观看| 久久亚洲AV永久无码精品| 亚欧日韩毛片在线看免费网站| 亚洲欧洲另类春色校园网站| 全亚洲最新黄色特级网站 | 三根一起会坏掉的好痛免费三级全黄的视频在线观看 | 国产日本亚洲一区二区三区| 日本免费电影一区| 日韩精品无码免费专区网站| 亚洲一级在线观看| 亚洲国产精品综合久久一线| 日本在线看片免费| 亚洲成AV人片高潮喷水| 亚洲精品V欧洲精品V日韩精品 | 9420免费高清在线视频| 色天使亚洲综合一区二区| 国产V亚洲V天堂无码久久久| 四虎国产精品免费久久|