<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

    #

    Dino Esposito
    Wintellect

    2003 年 8 月

    適用于:
        Microsoft? ASP.NET

    摘要:了解為 ASP.NET Web 頁面建立的事件模型,以及 Web 頁面轉變為 HTML 過程中的各個階段。ASP.NET HTTP 運行時負責管理對象管道,這些對象首先將請求的 URL 轉換成 Page 類的具體實例,然后再將這些實例轉換成純 HTML 文本。本文將探討那些作為頁面生命周期標志的事件,以及控件和頁面編寫者如何干預并改變標準行為。(本文包含一些指向英文站點的鏈接。)

    目錄

    簡介
    真正的 Page 類
    頁面的生命周期
    執行的各個階段
    小結

    簡介

    對由 Microsoft? Internet 信息服務 (IIS) 處理的 Microsoft? ASP.NET 頁面的每個請求都會被移交到 ASP.NET HTTP 管道。HTTP 管道由一系列托管對象組成,這些托管對象按順序處理請求,并將 URL 轉換為純 HTML 文本。HTTP 管道的入口是 HttpRuntime 類。ASP.NET 結構為輔助進程中的每個 AppDomain 創建一個此類的實例。(請注意,輔助進程為每個當前正在運行的 ASP.NET 應用程序維護一個特定的 AppDomain。)

    HttpRuntime 類從內部池中獲取 HttpApplication 對象,并安排此對象來處理請求。HTTP 應用程序管理器完成的主要任務就是找到將真正處理請求的類。當請求 .aspx 資源時,處理程序就是頁面處理程序,即從 Page 繼承的類的實例。資源類型和處理程序類型之間的關聯關系存儲在應用程序的配置文件中。更確切地說,默認的映射集是在 machine.config 文件的 <httpHandlers> 部分定義的。但是,應用程序可以在本地的 web.config 文件中自定義自己的 HTTP 處理程序列表。以下這一行代碼就是用來為 .aspx 資源定義 HTTP 處理程序的。

    <add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/>

    擴展名可以與處理程序類相關聯,并且更多是與處理程序工廠類相關聯。在所有情況下,負責處理請求的 HttpApplication 對象都會獲得一個實現 IHttpHandler 接口的對象。如果根據 HTTP 處理程序來解析關聯的資源/類,則返回的類將直接實現接口。如果資源被綁定到處理程序工廠,則還需要額外的步驟。處理程序工廠類實現 IHttpHandlerFactory 接口,此接口的 GetHandler 方法將返回一個基于 IHttpHandler 的對象。

    HTTP 運行時是如何結束這個循環并處理頁面請求的?ProcessRequest 方法在 IHttpHandler 接口中非常重要。通過對代表被請求頁面的對象調用此方法,ASP.NET 結構會啟動將生成瀏覽器輸出的進程。

    真正的 Page 類

    特定頁面的 HTTP 處理程序類型取決于 URL。首次調用 URL 時,將構建一個新的類,這個類被動態編譯為一個程序集。檢查 .aspx 資源的分析進程的結果是類的源代碼。該類被定義為命名空間 ASP 的組成部分,并且被賦予了一個模擬原始 URL 的名稱。例如,如果 URL 的終點是 page.aspx,則類的名稱就是 ASP.Page_aspx。不過,類的名稱可以通過編程方式來控制,方法是在 @Page 指令中設置 ClassName 屬性。

    HTTP 處理程序的基類是 Page。這個類定義了由所有頁面處理程序共享的方法和屬性的最小集合。Page 類實現 IHttpHandler 接口。

    在很多情況下,實際處理程序的基類并不是 Page,而是其他的類。例如,如果使用了代碼分離,就會出現這種情況。代碼分離是一項開 發技術,它可以將頁面所需的代碼隔離到單獨的 C# 和 Microsoft Visual Basic? .NET 類中。頁面的代碼是一組事件處理程序和輔助方法,這些處理程序和方法真正決定了頁面的行為。可以使用 <script runat=server> 標記對此代碼進行內聯定義,或者將其放置在外部類(代碼分離類)中。代碼分離類是從 Page 繼承并使用額外的方法的類,被指定用作 HTTP 處理程序的基類。

    還有一種情況,HTTP 處理程序也不是基于 Page 的,即在應用程序配置文件的 <pages> 部分中,包含了 PageBaseType 屬性的重新定義。

    <pages PageBaseType="Classes.MyPage, mypage" />

    PageBaseType 屬性指明包含頁面處理程序的基類的類型和程序集。從 Page 導出的這個類可以自動賦予處理程序擴展的自定義方法和屬性集。

    頁面的生命周期

    完全識別 HTTP 頁面處理程序類后,ASP.NET 運行時將調用處理程序的 ProcessRequest 方法來處理請求。通常情況下,無需更改此方法的實現,因為它是由 Page 類提供的。

    此實現將從調用為頁面構建控件樹的 FrameworkInitialize 方法開始。FrameworkInitialize 方法是 TemplateControl 類(Page 本身從此類導出)的一個受保護的虛擬成員。所有為 .aspx 資源動態生成的處理程序都將覆蓋 FrameworkInitialize。在此方法中,構建了頁面的整個控件樹。

    接下來,ProcessRequest 使頁面經歷了各個階段:初始化、加載視圖狀態信息和回發數據、加載頁面的用戶代碼以及執行回發服務器端事件。之后,頁面進入顯示模式:收集更新的視圖狀態,生成 HTML 代碼并隨后將代碼發送到輸出控制臺。最后,卸載頁面,并認為請求處理完畢。

    在各個階段中,頁面會觸發少數幾個事件,這些事件可以由 Web 控件和用戶定義的代碼截取并進行處理。其中的一些事件是嵌入式控件專用的,因此無法在 .aspx 代碼級進行處理。

    要處理特定事件的頁面應該明確注冊一個適合的處理程序。不過,為了向后兼容早期的 Visual Basic 編程風格,ASP.NET 也支持隱式事件掛鉤的形式。默認情況下,頁面會嘗試將特定的方法名稱與事件相匹配,如果實現匹配,則認為此方法就是匹配事件的處理程序。ASP.NET 提供了六種方法名稱的特定識別,它們是 Page_InitPage_LoadPage_DataBindPage_PreRenderPage_Unload。這些方法被認為是由 Page 類提供的相應事件的處理程序。HTTP 運行時會自動將這些方法綁定到頁面事件,這樣,開發人員就不必再編寫所需的粘接代碼了。例如,如果命名為 Page_Load 的方法綁定到頁面的 Load 事件,則可省去以下代碼。

    this.Load += new EventHandler(this.Page_Load);

    對特定名稱的自動識別是由 @Page 指令的 AutoEventWireup 屬性控制的。如果該屬性設置為 false,則要處理事件的所有應用程序都需要明確連接到頁面事件。不使用自動綁定事件的頁面性能會稍好一些,因為不需要額外匹配名稱與事件。請注意,所有 Microsoft Visual Studio? .NET 項目都是在禁用 AutoEventWireup 屬性的情況下創建的。但是,該屬性的默認設置是 true,即 Page_Load 等方法會被識別,并被綁定到相關聯的事件。

    下表中按順序列出了頁面的執行包括的幾個階段,執行的標志是一些應用程序級的事件和/或受保護并可覆蓋的方法。

    表 1:ASP.NET 頁面生命中的關鍵事件

    階段 頁面事件 可覆蓋的方法
    頁面初始化 Init  
    加載視圖狀態   LoadViewState
    處理回發數據   任意實現 IPostBackDataHandler 接口的控件中的 LoadPostData 方法
    加載頁面 Load  
    回發更改通知   任意實現 IPostBackDataHandler 接口的控件中的 RaisePostDataChangedEvent 方法
    處理回發事件 由控件定義的任意回發事件 任意實現 IPostBackDataHandler 接口的控件中的 RaisePostBackEvent 方法
    頁面顯示前階段 PreRender  
    保存視圖狀態   SaveViewState
    顯示頁面   Render
    卸載頁面 Unload  

    以上所列的階段中有些在頁面級是不可見的,并且僅對服務器控件的編寫者和要創建從 Page 導出的類的開發人員有意義。InitLoadPreRenderUnload,再加上由嵌入式控件定義的所有回發事件,就構成了向外發送頁面的各個階段標記。

    執行的各個階段

    頁面生命周期中的第一個階段是初始化。這個階段的標志是 Init 事件。在成功創建頁面的控件樹后,將對應用程序觸發此事件。換句話說,當 Init 事件發生時,.aspx 源文件中靜態聲明的所有控件都已實例化并采用各自的默認值。控件可以截取 Init 事件以初始化在傳入的 Web 請求的生命周期內所需的所有設置。例如,這時控件可以加載外部模板文件或設置事件的處理程序。請注意,這時視圖狀態信息尚不可用。

    初始化之后,頁面框架將加載頁面的視圖狀態。視圖狀態是名稱/值對的集合,在此集合中,控件和頁面本身存儲了對所有 Web 請求都必須始終有效的全部信息。視圖狀態代表了頁面的調用上下文。通常,它包含上次在服務器上處理頁面時控件的狀態。首次在會話中請求頁面時,視圖狀態為 空。默認情況下,視圖狀態存儲在靜默添加到頁面的隱藏字段中,該字段的名稱是 __VIEWSTATE。通過覆蓋 LoadViewState 方法(Control 類的受保護、可覆蓋方法),組件開發人員可以控制視圖狀態的存儲方式以及視圖狀態的內容映射到內部狀態的方式。

    有些方法(如 LoadPageStateFromPersistenceMedium 以及其對應的 SavePageStateToPersistenceMedium),可以用來將視圖狀態加載并保存到其他存儲介質(例如會話、數據庫或服務器端文件)中。與 LoadViewState 不同,上述方法只能在從 Page 導出的類中使用。

    存儲視圖狀態之后,頁面樹中控件的狀態與頁面最后一次顯示在瀏覽器中的狀態相同。下一步是更新它們的狀態以加入客戶端的更改。處理回發數據階段使控件有機會更新其狀態,從而準確反映客戶端相應的 HTML 元素的狀態。例如,服務器的 TextBox 控件對應的 HTML 元素是 <input type=text>。在回發數據階段,TextBox 控件將檢索 <input> 標記的當前值,并使用該值來刷新自己內部的狀態。每個控件都要從回發的數據中提取值并更新自己的部分屬性。TextBox 控件將更新它的 Text 屬性,而 CheckBox 控件將刷新它的 Checked 屬性。服務器控件和 HTML 元素的對應關系可以通過二者的 ID 找到。

    在處理回發數據階段的最后,頁面中的所有控件的狀態都將使用客戶端輸入的更改來更新前一狀態。這時,將對頁面觸發 Load 事件。

    頁面中可能會有一些控件,當其某個敏感屬性在兩個不同的請求中被修改時,需要完成特定的任務。例如,如果 TextBox 控件的文本在客戶端被修改,則此控件將觸發 TextChanged 事件。每個控件在其一個或多個屬性被修改為客戶端輸入的值時都可以決定觸發相應的事件。對于這些更改對其非常關鍵的控件,控件實現 IPostBackDataHandler 接口,此接口的 LoadPostData 方法是在 Load 事件后立即調用的。通過對 LoadPostData 方法進行編碼,控件將驗證自上次請求后是否發生了關鍵更改,并觸發自己的更改事件。

    頁面生命周期中的關鍵事件是被調用以執行服務器端代碼的事件,此代碼與客戶端觸發的事件相關聯。當用戶單擊按鈕時,將回發頁面。回發值的集合中包括啟動整個操作的按鈕的 ID。如果控件實現 IPostBackEventHandler 接口(如按鈕和鏈接按鈕),頁面框架將調用 RaisePostBackEvent 方法。此方法的行為取決于控件的類型。就按鈕和鏈接按鈕而言,此方法將查找 Click 事件處理程序并運行相關的委托。

    處理完回發事件之后,頁面就可以顯示了。這個階段的標志是 PreRender 事件。控件可以利用這段時間來執行那些需要在保存視圖狀態和顯示輸出的前一刻執行的更新操作。下一個狀態是 SaveViewState,在此狀態中,所有控件和頁面本身都將更新自己 ViewState 集合的內容。然后,將得到序列化、散列、Base64 編碼的視圖狀態,而且此視圖狀態與隱藏字段 __VIEWSTATE 相關聯。

    通過覆蓋 Render 方法可以改變各個控件的顯示機制。此方法接受 HTML 書寫器對象,并使用此對象來積累所有要為控件生成的 HTML 文本。Page 類的 Render 方法的默認實現包括對所有成員控件的遞歸調用。對于每個控件,頁面都將調用 Render 方法,并緩存 HTML 輸出。

    頁面生命中的最后一個標志是 Unload 事件,在頁面對象消除之前發生。在此事件中,您應該釋放所有可能占用的關鍵資源(例如文件、圖形對象、數據庫連接等)。

    在此事件之后,也就是最后,瀏覽器接收 HTTP 響應數據包并顯示頁面。

    小結

    ASP.NET 頁面對象模型因其事件機制而顯得格外新穎獨特。Web 頁面由控件組成,這些控件既可以產生豐富的基于 HTML 的用戶界面,又可以通過事件與用戶交互。以前,在 Web 應用程序的上下文中設置事件模型是件有挑戰性的工作。可我們驚奇的看到,客戶端生成的事件可以由服務器端的代碼來解決,而且只進行一些相應的修改后,此過 程仍可以輸出相同的 HTML 頁面。

    掌握這個模型對于了解頁面生命周期的各個階段,以及頁面對象如何被 HTTP 運行時實例化并使用是非常重要的。

    關于作者

    Dino Esposito 是一位來自意大利羅馬的培訓教師和顧問。作為 Wintellect 團隊的成員,Dino 專門研究 ASP.NET 和 ADO.NET,主要在歐洲和美國從事教學和咨詢工作。此外,Dino 還負責管理 Wintellect 的 ADO.NET 課件,并為 MSDN 期刊的“Cutting Edge”專欄撰寫文章。要與他聯系,請向 dinoe@wintellect.com 發送電子郵件。

    發表于 2003年11月4日 8:18

    posted @ 2006-01-05 15:03 javaGrowing 閱讀(247) | 評論 (0)編輯 收藏

    Session詳解
    作者:郎云鵬    來自:dev2dev

    作者:郎云鵬(dev2dev ID: hippiewolf)

    摘要:雖然session機制在web應用程序中被采用已經很長時間了,但是仍然有很多人不清楚session機制的本質,以至不能正確的應用這一 技術。本文將詳細討論session的工作機制并且對在Java web application中應用session機制時常見的問題作出解答。

    目錄:
    一、術語session
    二、HTTP協議與狀態保持
    三、理解cookie機制
    四、理解session機制
    五、理解javax.servlet.http.HttpSession
    六、HttpSession常見問題
    七、跨應用程序的session共享
    八、總結
    參考文檔

    一、術語session
    在我的經驗里,session這個詞被濫用的程度大概僅次于transaction,更加有趣的是transaction與session在某些語境下的含義是相同的。

    session,中文經常翻譯為會話,其本來的含義是指有始有終的一系列動作/消息,比如打電話時從拿起電話撥號到掛斷電話這中間的一系列過程可以 稱之為一個session。有時候我們可以看到這樣的話“在一個瀏覽器會話期間,...”,這里的會話一詞用的就是其本義,是指從一個瀏覽器窗口打開到關 閉這個期間①。最混亂的是“用戶(客戶端)在一次會話期間”這樣一句話,它可能指用戶的一系列動作(一般情況下是同某個具體目的相關的一系列動作,比如從 登錄到選購商品到結賬登出這樣一個網上購物的過程,有時候也被稱為一個transaction),然而有時候也可能僅僅是指一次連接,也有可能是指含義 ①,其中的差別只能靠上下文來推斷②。

    然而當session一詞與網絡協議相關聯時,它又往往隱含了“面向連接”和/或“保持狀態”這樣兩個含義,“面向連接”指的是在通信雙方在通信之 前要先建立一個通信的渠道,比如打電話,直到對方接了電話通信才能開始,與此相對的是寫信,在你把信發出去的時候你并不能確認對方的地址是否正確,通信渠 道不一定能建立,但對發信人來說,通信已經開始了。“保持狀態”則是指通信的一方能夠把一系列的消息關聯起來,使得消息之間可以互相依賴,比如一個服務員 能夠認出再次光臨的老顧客并且記得上次這個顧客還欠店里一塊錢。這一類的例子有“一個TCP session”或者“一個POP3 session”③。

    而到了web服務器蓬勃發展的時代,session在web開發語境下的語義又有了新的擴展,它的含義是指一類用來在客戶端與服務器之間保持狀態的 解決方案④。有時候session也用來指這種解決方案的存儲結構,如“把xxx保存在session里”⑤。由于各種用于web開發的語言在一定程度上 都提供了對這種解決方案的支持,所以在某種特定語言的語境下,session也被用來指代該語言的解決方案,比如經常把Java里提供的 javax.servlet.http.HttpSession簡稱為session⑥。

    鑒于這種混亂已不可改變,本文中session一詞的運用也會根據上下文有不同的含義,請大家注意分辨。
    在本文中,使用中文“瀏覽器會話期間”來表達含義①,使用“session機制”來表達含義④,使用“session”表達含義⑤,使用具體的“HttpSession”來表達含義⑥

    二、HTTP協議與狀態保持
    HTTP協議本身是無狀態的,這與HTTP協議本來的目的 是相符的,客戶端只需要簡單的向服務器請求下載某些文件,無論是客戶端還是服務器都沒有必要紀錄彼此過去的行為,每一次請求之間都是獨立的,好比一個顧客 和一個自動售貨機或者一個普通的(非會員制)大賣場之間的關系一樣。

    然而聰明(或者貪心?)的人們很快發現如果能夠提供一些按需生成的動態信息會使web變得更加有用,就像給有線電視加上點播功能一樣。這種需求一方 面迫使HTML逐步添加了表單、腳本、DOM等客戶端行為,另一方面在服務器端則出現了CGI規范以響應客戶端的動態請求,作為傳輸載體的HTTP協議也 添加了文件上載、cookie這些特性。其中cookie的作用就是為了解決HTTP協議無狀態的缺陷所作出的努力。至于后來出現的session機制則 是又一種在客戶端與服務器之間保持狀態的解決方案。

    讓我們用幾個例子來描述一下cookie和session機制之間的區別與聯系。筆者曾經常去的一家咖啡店有喝5杯咖啡免費贈一杯咖啡的優惠,然而一次性消費5杯咖啡的機會微乎其微,這時就需要某種方式來紀錄某位顧客的消費數量。想象一下其實也無外乎下面的幾種方案:
    1、該店的店員很厲害,能記住每位顧客的消費數量,只要顧客一走進咖啡店,店員就知道該怎么對待了。這種做法就是協議本身支持狀態。
    2、發給顧客一張卡片,上面記錄著消費的數量,一般還有個有效期限。每次消費時,如果顧客出示這張卡片,則此次消費就會與以前或以后的消費相聯系起來。這種做法就是在客戶端保持狀態。
    3、發給顧客一張會員卡,除了卡號之外什么信息也不紀錄,每次消費時,如果顧客出示該卡片,則店員在店里的紀錄本上找到這個卡號對應的紀錄添加一些消費信息。這種做法就是在服務器端保持狀態。

    由于HTTP協議是無狀態的,而出于種種考慮也不希望使之成為有狀態的,因此,后面兩種方案就成為現實的選擇。具體來說cookie機制采用的是在 客戶端保持狀態的方案,而session機制采用的是在服務器端保持狀態的方案。同時我們也看到,由于采用服務器端保持狀態的方案在客戶端也需要保存一個 標識,所以session機制可能需要借助于cookie機制來達到保存標識的目的,但實際上它還有其他選擇。

    三、理解cookie機制
    cookie機制的基本原理就如上面的例子一樣簡單,但是還有幾個問題需要解決:“會員卡”如何分發;“會員卡”的內容;以及客戶如何使用“會員卡”。

    正統的cookie分發是通過擴展HTTP協議來實現的,服務器通過在HTTP的響應頭中加上一行特殊的指示以提示瀏覽器按照指示生成相應的cookie。然而純粹的客戶端腳本如JavaScript或者VBScript也可以生成cookie。

    而cookie的使用是由瀏覽器按照一定的原則在后臺自動發送給服務器的。瀏覽器檢查所有存儲的cookie,如果某個cookie所聲明的作用范 圍大于等于將要請求的資源所在的位置,則把該cookie附在請求資源的HTTP請求頭上發送給服務器。意思是麥當勞的會員卡只能在麥當勞的店里出示,如 果某家分店還發行了自己的會員卡,那么進這家店的時候除了要出示麥當勞的會員卡,還要出示這家店的會員卡。

    cookie的內容主要包括:名字,值,過期時間,路徑和域。
    其中域可以指定某一個域比如.google.com,相當于總店招牌,比如寶潔公司,也可以指定一個域下的具體某臺機器比如www.google.com或者froogle.google.com,可以用飄柔來做比。
    路徑就是跟在域名后面的URL路徑,比如/或者/foo等等,可以用某飄柔專柜做比。
    路徑與域合在一起就構成了cookie的作用范圍。
    如 果不設置過期時間,則表示這個cookie的生命期為瀏覽器會話期間,只要關閉瀏覽器窗口,cookie就消失了。這種生命期為瀏覽器會話期的 cookie被稱為會話cookie。會話cookie一般不存儲在硬盤上而是保存在內存里,當然這種行為并不是規范規定的。如果設置了過期時間,瀏覽器 就會把cookie保存到硬盤上,關閉后再次打開瀏覽器,這些cookie仍然有效直到超過設定的過期時間。

    存儲在硬盤上的cookie可以在不同的瀏覽器進程間共享,比如兩個IE窗口。而對于保存在內存里的cookie,不同的瀏覽器有不同的處理方式。 對于IE,在一個打開的窗口上按Ctrl-N(或者從文件菜單)打開的窗口可以與原窗口共享,而使用其他方式新開的IE進程則不能共享已經打開的窗口的內 存cookie;對于Mozilla Firefox0.8,所有的進程和標簽頁都可以共享同樣的cookie。一般來說是用javascript的window.open打開的窗口會與原窗 口共享內存cookie。瀏覽器對于會話cookie的這種只認cookie不認人的處理方式經常給采用session機制的web應用程序開發者造成很 大的困擾。

    下面就是一個goolge設置cookie的響應頭的例子
    HTTP/1.1 302 Found
    Location: http://www.google.com/intl/zh-CN/
    Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
    Content-Type: text/html


    這是使用HTTPLook這個HTTP Sniffer軟件來俘獲的HTTP通訊紀錄的一部分


    瀏覽器在再次訪問goolge的資源時自動向外發送cookie


    使用Firefox可以很容易的觀察現有的cookie的值
    使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。


    IE也可以設置在接受cookie前詢問


    這是一個詢問接受cookie的對話框。

    四、理解session機制
    session機制是一種服務器端的機制,服務器使用一種類似于散列表的結構(也可能就是使用散列表)來保存信息。

    當程序需要為某個客戶端的請求創建一個session的時候,服務器首先檢查這個客戶端的請求里是否已包含了一個session標識 - 稱為session id,如果已包含一個session id則說明以前已經為此客戶端創建過session,服務器就按照session id把這個session檢索出來使用(如果檢索不到,可能會新建一個),如果客戶端請求不包含session id,則為此客戶端創建一個session并且生成一個與此session相關聯的session id,session id的值應該是一個既不會重復,又不容易被找到規律以仿造的字符串,這個session id將被在本次響應中返回給客戶端保存。

    保存這個session id的方式可以采用cookie,這樣在交互過程中瀏覽器可以自動的按照規則把這個標識發揮給服務器。一般這個cookie的名字都是類似于 SEEESIONID,而。比如weblogic對于web應用程序生成的cookie,JSESSIONID= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是 JSESSIONID。

    由于cookie可以被人為的禁止,必須有其他機制以便在cookie被禁止時仍然能夠把session id傳遞回服務器。經常被使用的一種技術叫做URL重寫,就是把session id直接附加在URL路徑的后面,附加方式也有兩種,一種是作為URL路徑的附加信息,表現形式為http://...../xxx; jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
    另一種是作為查詢字符串附加在URL后面,表現形式為http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
    這兩種方式對于用戶來說是沒有區別的,只是服務器在解析的時候處理的方式不同,采用第一種方式也有利于把session id的信息和正常程序參數區分開來。
    為了在整個交互過程中始終保持狀態,就必須在每個客戶端可能請求的路徑后面都包含這個session id。

    另一種技術叫做表單隱藏字段。就是服務器會自動修改表單,添加一個隱藏字段,以便在表單提交時能夠把session id傳遞回服務器。比如下面的表單
    <form name="testform" action="/xxx">
    <input type="text">
    </form>
    在被傳遞給客戶端之前將被改寫成
    <form name="testform" action="/xxx">
    <input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
    <input type="text">
    </form>
    這種技術現在已較少應用,筆者接觸過的很古老的iPlanet6(SunONE應用服務器的前身)就使用了這種技術。
    實際上這種技術可以簡單的用對action應用URL重寫來代替。

    在談論session機制的時候,常常聽到這樣一種誤解“只要關閉瀏覽器,session就消失了”。其實可以想象一下會員卡的例子,除非顧客主動 對店家提出銷卡,否則店家絕對不會輕易刪除顧客的資料。對session來說也是一樣的,除非程序通知服務器刪除一個session,否則服務器會一直保 留,程序一般都是在用戶做log off的時候發個指令去刪除session。然而瀏覽器從來不會主動在關閉之前通知服務器它將要關閉,因此服務器根本不會有機會知道瀏覽器已經關閉,之所 以會有這種錯覺,是大部分session機制都使用會話cookie來保存session id,而關閉瀏覽器后這個session id就消失了,再次連接服務器時也就無法找到原來的session。如果服務器設置的cookie被保存到硬盤上,或者使用某種手段改寫瀏覽器發出的 HTTP請求頭,把原來的session id發送給服務器,則再次打開瀏覽器仍然能夠找到原來的session。

    恰恰是由于關閉瀏覽器不會導致session被刪除,迫使服務器為seesion設置了一個失效時間,當距離客戶端上一次使用session的時間超過這個失效時間時,服務器就可以認為客戶端已經停止了活動,才會把session刪除以節省存儲空間。

    五、理解javax.servlet.http.HttpSession
    HttpSession是Java平臺對session機制的實現規范,因為它僅僅是個接口,具體到每個web應用服務器的提供商,除了對規范支持之外,仍然會有一些規范里沒有規定的細微差異。這里我們以BEA的Weblogic Server8.1作為例子來演示。

    首先,Weblogic Server提供了一系列的參數來控制它的HttpSession的實現,包括使用cookie的開關選項,使用URL重寫的開關選項,session持 久化的設置,session失效時間的設置,以及針對cookie的各種設置,比如設置cookie的名字、路徑、域,cookie的生存時間等。

    一般情況下,session都是存儲在內存里,當服務器進程被停止或者重啟的時候,內存里的session也會被清空,如果設置了session的 持久化特性,服務器就會把session保存到硬盤上,當服務器進程重新啟動或這些信息將能夠被再次使用,Weblogic Server支持的持久性方式包括文件、數據庫、客戶端cookie保存和復制。

    復制嚴格說來不算持久化保存,因為session實際上還是保存在內存里,不過同樣的信息被復制到各個cluster內的服務器進程中,這樣即使某個服務器進程停止工作也仍然可以從其他進程中取得session。

    cookie生存時間的設置則會影響瀏覽器生成的cookie是否是一個會話cookie。默認是使用會話cookie。有興趣的可以用它來試驗我們在第四節里提到的那個誤解。

    cookie的路徑對于web應用程序來說是一個非常重要的選項,Weblogic Server對這個選項的默認處理方式使得它與其他服務器有明顯的區別。后面我們會專題討論。

    關于session的設置參考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869

    六、HttpSession常見問題
    (在本小節中session的含義為⑤和⑥的混合)


    1、session在何時被創建
    一個常見的誤解是以為session在有客戶端訪問時就被創建,然而事實是直到某server端程 序調用HttpServletRequest.getSession(true)這樣的語句時才被創建,注意如果JSP沒有顯示的使用 <%@page session="false"%> 關閉session,則JSP文件在編譯成Servlet時將會自動加上這樣一條語句HttpSession session = HttpServletRequest.getSession(true);這也是JSP中隱含的session對象的來歷。

    由于session會消耗內存資源,因此,如果不打算使用session,應該在所有的JSP中關閉它。

    2、session何時被刪除
    綜合前面的討論,session在下列情況下被刪除a.程序調用HttpSession.invalidate();或b.距離上一次收到客戶端發送的session id時間間隔超過了session的超時設置;或c.服務器進程被停止(非持久session)

    3、如何做到在瀏覽器關閉時刪除session
    嚴格的講,做不到這一點。可以做一點努力的辦法是在所有的客戶端頁面里使用javascript代碼window.oncolose來監視瀏覽器的關閉動作,然后向服務器發送一個請求來刪除session。但是對于瀏覽器崩潰或者強行殺死進程這些非常規手段仍然無能為力。

    4、有個HttpSessionListener是怎么回事
    你可以創建這樣的listener去監控session的創建和銷毀事件,使得 在發生這樣的事件時你可以做一些相應的工作。注意是session的創建和銷毀動作觸發listener,而不是相反。類似的與HttpSession有 關的listener還有HttpSessionBindingListener,HttpSessionActivationListener和 HttpSessionAttributeListener。

    5、存放在session中的對象必須是可序列化的嗎
    不是必需的。要求對象可序列化只是為了session能夠在集群中被復制或者能夠持久 保存或者在必要時server能夠暫時把session交換出內存。在Weblogic Server的session中放置一個不可序列化的對象在控制臺上會收到一個警告。我所用過的某個iPlanet版本如果session中有不可序列化 的對象,在session銷毀時會有一個Exception,很奇怪。

    6、如何才能正確的應付客戶端禁止cookie的可能性
    對所有的URL使用URL重寫,包括超鏈接,form的action,和重定向的URL,具體做法參見[6]
    http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770

    7、開兩個瀏覽器窗口訪問應用程序會使用同一個session還是不同的session
    參見第三小節對cookie的討論,對session來說是只認id不認人,因此不同的瀏覽器,不同的窗口打開方式以及不同的cookie存儲方式都會對這個問題的答案有影響。

    8、如何防止用戶打開兩個瀏覽器窗口操作導致的session混亂
    這個問題與防止表單多次提交是類似的,可以通過設置客戶端的令牌來解決。 就是在服務器每次生成一個不同的id返回給客戶端,同時保存在session里,客戶端提交表單時必須把這個id也返回服務器,程序首先比較返回的id與 保存在session里的值是否一致,如果不一致則說明本次操作已經被提交過了。可以參看《J2EE核心模式》關于表示層模式的部分。需要注意的是對于使 用javascript window.open打開的窗口,一般不設置這個id,或者使用單獨的id,以防主窗口無法操作,建議不要再window.open打開的窗口里做修改 操作,這樣就可以不用設置。

    9、為什么在Weblogic Server中改變session的值后要重新調用一次session.setValue
    做這個動作主要是為了在集群環境中提示Weblogic Server session中的值發生了改變,需要向其他服務器進程復制新的session值。

    10、為什么session不見了
    排除session正常失效的因素之外,服務器本身的可能性應該是微乎其微的,雖然筆者在 iPlanet6SP1加若干補丁的Solaris版本上倒也遇到過;瀏覽器插件的可能性次之,筆者也遇到過3721插件造成的問題;理論上防火墻或者代 理服務器在cookie處理上也有可能會出現問題。
    出現這一問題的大部分原因都是程序的錯誤,最常見的就是在一個應用程序中去訪問另外一個應用程序。我們在下一節討論這個問題。

    七、跨應用程序的session共享

    常常有這樣的情況,一個大項目被分割成若干小項目開發,為了能夠互不干擾,要 求每個小項目作為一個單獨的web應用程序開發,可是到了最后突然發現某幾個小項目之間需要共享一些信息,或者想使用session來實現SSO (single sign on),在session中保存login的用戶信息,最自然的要求是應用程序間能夠訪問彼此的session。

    然而按照Servlet規范,session的作用范圍應該僅僅限于當前應用程序下,不同的應用程序之間是不能夠互相訪問對方的session的。 各個應用服務器從實際效果上都遵守了這一規范,但是實現的細節卻可能各有不同,因此解決跨應用程序session共享的方法也各不相同。

    首先來看一下Tomcat是如何實現web應用程序之間session的隔離的,從Tomcat設置的cookie路徑來看,它對不同的應用程序設 置的cookie路徑是不同的,這樣不同的應用程序所用的session id是不同的,因此即使在同一個瀏覽器窗口里訪問不同的應用程序,發送給服務器的session id也可以是不同的。

    根據這個特性,我們可以推測Tomcat中session的內存結構大致如下。

    筆者以前用過的iPlanet也采用的是同樣的方式,估計SunONE與iPlanet之間不會有太大的差別。對于這種方式的服務器,解決的思路很 簡單,實際實行起來也不難。要么讓所有的應用程序共享一個session id,要么讓應用程序能夠獲得其他應用程序的session id。

    iPlanet中有一種很簡單的方法來實現共享一個session id,那就是把各個應用程序的cookie路徑都設為/(實際上應該是/NASApp,對于應用程序來講它的作用相當于根)。
    <session-info>
    <path>/NASApp</path>
    </session-info>

    需要注意的是,操作共享的session應該遵循一些編程約定,比如在session attribute名字的前面加上應用程序的前綴,使得setAttribute("name", "neo")變成setAttribute("app1.name", "neo"),以防止命名空間沖突,導致互相覆蓋。


    在Tomcat中則沒有這么方便的選擇。在Tomcat版本3上,我們還可以有一些手段來共享session。對于版本4以上的 Tomcat,目前筆者尚未發現簡單的辦法。只能借助于第三方的力量,比如使用文件、數據庫、JMS或者客戶端cookie,URL參數或者隱藏字段等手 段。

    我們再看一下Weblogic Server是如何處理session的。

    從截屏畫面上可以看到Weblogic Server對所有的應用程序設置的cookie的路徑都是/,這是不是意味著在Weblogic Server中默認的就可以共享session了呢?然而一個小實驗即可證明即使不同的應用程序使用的是同一個session,各個應用程序仍然只能訪問 自己所設置的那些屬性。這說明Weblogic Server中的session的內存結構可能如下

    對于這樣一種結構,在session機制本身上來解決session共享的問題應該是不可能的了。除了借助于第三方的力量,比如使用文件、數據庫、 JMS或者客戶端cookie,URL參數或者隱藏字段等手段,還有一種較為方便的做法,就是把一個應用程序的session放到 ServletContext中,這樣另外一個應用程序就可以從ServletContext中取得前一個應用程序的引用。示例代碼如下,

    應用程序A
    context.setAttribute("appA", session);

    應用程序B
    contextA = context.getContext("/appA");
    HttpSession sessionA = (HttpSession)contextA.getAttribute("appA");

    值得注意的是這種用法不可移植,因為根據ServletContext的JavaDoc,應用服務器可以處于安全的原因對于context.getContext("/appA");返回空值,以上做法在Weblogic Server 8.1中通過。

    那么Weblogic Server為什么要把所有的應用程序的cookie路徑都設為/呢?原來是為了SSO,凡是共享這個session的應用程序都可以共享認證的信息。一 個簡單的實驗就可以證明這一點,修改首先登錄的那個應用程序的描述符weblogic.xml,把cookie路徑修改為/appA訪問另外一個應用程序 會重新要求登錄,即使是反過來,先訪問cookie路徑為/的應用程序,再訪問修改過路徑的這個,雖然不再提示登錄,但是登錄的用戶信息也會丟失。注意做 這個實驗時認證方式應該使用FORM,因為瀏覽器和web服務器對basic認證方式有其他的處理方式,第二次請求的認證不是通過session來實現 的。具體請參看[7] secion 14.8 Authorization,你可以修改所附的示例程序來做這些試驗。

    八、總結
    session機制本身并不復雜,然而其實現和配置上的靈活性卻使得具體情況復雜多變。這也要求我們不能把僅僅某一次的經驗或者某一個瀏覽器,服務器的經驗當作普遍適用的經驗,而是始終需要具體情況具體分析。

    關于作者:
    郎云鵬(dev2dev ID: hippiewolf),軟件工程師,從事J2EE開發
    電子郵件:langyunpeng@yahoo.com.cn
    地址:大連軟件園路31號科技大廈A座大連博涵咨詢服務有限公司

    參考文檔:
    [1] Preliminary Specification http://wp.netscape.com/newsref/std/cookie_spec.html
    [2] RFC2109 http://www.rfc-editor.org/rfc/rfc2109.txt
    [3] RFC2965 http://www.rfc-editor.org/rfc/rfc2965.txt
    [4] The Unofficial Cookie FAQ http://www.cookiecentral.com/faq/
    [5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869
    [6] http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770
    [7] RFC2616 http://www.rfc-editor.org/rfc/rfc2616.txt

    posted @ 2006-01-04 22:22 javaGrowing 閱讀(399) | 評論 (0)編輯 收藏

    作者:Brian Goetz    來自:developerWorks 中國

      雖然用 Java? 語言編寫的程序在理論上是不會出現“內存泄漏”的,但是有時對象在不再作為程序的邏輯狀態的一部分之后仍然不被垃圾收集。本月,負責保障應用程序健康的工程師 Brian Goetz 探討了無意識的對象保留的常見原因,并展示了如何用弱引用堵住泄漏。
      要讓垃圾收集(GC)回收程序不再使用的對象,對象的邏輯 生命周期(應用程序使用它的時間)和對該對象擁有的引用的實際 生命周期必須是相同的。在大多數時候,好的軟件工程技術保證這是自動實現的,不用我們對對象生命周期問題花費過多心思。但是偶爾我們會創建一個引用,它在 內存中包含對象的時間比我們預期的要長得多,這種情況稱為無意識的對象保留(unintentional object retention)。

      全局 Map 造成的內存泄漏

       無意識對象保留最常見的原因是使用 Map 將元數據與臨時對象(transient object)相關聯。假定一個對象具有中等生命周期,比分配它的那個方法調用的生命周期長,但是比應用程序的生命周期短,如客戶機的套接字連接。需要將 一些元數據與這個套接字關聯,如生成連接的用戶的標識。在創建 Socket 時是不知道這些信息的,并且不能將數據添加到 Socket 對象上,因為不能控制 Socket 類或者它的子類。這時,典型的方法就是在一個全局 Map 中存儲這些信息,如清單 1 中的 SocketManager 類所示:

      清單 1. 使用一個全局 Map 將元數據關聯到一個對象

    public class SocketManager {
        private Map<Socket,User> m = new HashMap<Socket,User>();
       
        public void setUser(Socket s, User u) {
            m.put(s, u);
        }
        public User getUser(Socket s) {
            return m.get(s);
        }
        public void removeUser(Socket s) {
            m.remove(s);
        }
    }

    SocketManager socketManager;
    ...
    socketManager.setUser(socket, user);

      這種方法的問題是元數據的生命周期需要與套接字的生命周期掛鉤,但是除非準確地知道什么時候程序不再需要這個套接字,并記住從 Map 中刪除相應的映射,否則,Socket 和 User 對象將會永遠留在 Map 中,遠遠超過響應了請求和關閉套接字的時間。這會阻止 Socket 和 User 對象被垃圾收集,即使應用程序不會再使用它們。這些對象留下來不受控制,很容易造成程序在長時間運行后內存爆滿。除了最簡單的情況,在幾乎所有情況下找出什么時候 Socket 不再被程序使用是一件很煩人和容易出錯的任務,需要人工對內存進行管理。

      找出內存泄漏

       程序有內存泄漏的第一個跡象通常是它拋出一個 OutOfMemoryError,或者因為頻繁的垃圾收集而表現出糟糕的性能。幸運的是,垃圾收集可以提供能夠用來診斷內存泄漏的大量信息。如果以 -verbose:gc 或者 -Xloggc 選項調用 JVM,那么每次 GC 運行時在控制臺上或者日志文件中會打印出一個診斷信息,包括它所花費的時間、當前堆使用情況以及恢復了多少內存。記錄 GC 使用情況并不具有干擾性,因此如果需要分析內存問題或者調優垃圾收集器,在生產環境中默認啟用 GC 日志是值得的。

       有工具可以利用 GC 日志輸出并以圖形方式將它顯示出來,JTune 就是這樣的一種工具(請參閱 參考資料)。觀察 GC 之后堆大小的圖,可以看到程序內存使用的趨勢。對于大多數程序來說,可以將內存使用分為兩部分:baseline 使用和 current load 使用。對于服務器應用程序,baseline 使用就是應用程序在沒有任何負荷、但是已經準備好接受請求時的內存使用,current load 使用是在處理請求過程中使用的、但是在請求處理完成后會釋放的內存。只要負荷大體上是恒定的,應用程序通常會很快達到一個穩定的內存使用水平。如果在應用 程序已經完成了其初始化并且負荷沒有增加的情況下,內存使用持續增加,那么程序就可能在處理前面的請求時保留了生成的對象。

      清單 2 展示了一個有內存泄漏的程序。MapLeaker 在線程池中處理任務,并在一個 Map 中記錄每一項任務的狀態。不幸的是,在任務完成后它不會刪除那一項,因此狀態項和任務對象(以及它們的內部狀態)會不斷地積累。

      清單 2. 具有基于 Map 的內存泄漏的程序

    public class MapLeaker {
        public ExecutorService exec = Executors.newFixedThreadPool(5);
        public Map<Task, TaskStatus> taskStatus
            = Collections.synchronizedMap(new HashMap<Task, TaskStatus>());
        private Random random = new Random();

        private enum TaskStatus { NOT_STARTED, STARTED, FINISHED };

        private class Task implements Runnable {
            private int[] numbers = new int[random.nextInt(200)];

            public void run() {
                int[] temp = new int[random.nextInt(10000)];
                taskStatus.put(this, TaskStatus.STARTED);
                doSomeWork();
                taskStatus.put(this, TaskStatus.FINISHED);
            }
        }

        public Task newTask() {
            Task t = new Task();
            taskStatus.put(t, TaskStatus.NOT_STARTED);
            exec.execute(t);
            return t;
        }
    }

      圖 1 顯示 MapLeaker GC 之后應用程序堆大小隨著時間的變化圖。上升趨勢是存在內存泄漏的警示信號。(在真實的應用程序中,坡度不會這么大,但是在收集了足夠長時間的 GC 數據后,上升趨勢通常會表現得很明顯。)


    圖 1. 持續上升的內存使用趨勢

      確信有了內存泄漏后,下一步就是找出哪種對象造成了這個問題。所有內存分析器都可以生成按照對象類進行分解的堆快照。有一些很好的商業堆分析工具,但是找出內存泄漏不一定要花錢買這些工具 —— 內置的 hprof 工具也可完成這項工作。要使用 hprof 并讓它跟蹤內存使用,需要以 -Xrunhprof:heap=sites 選項調用 JVM。

      清單 3 顯示分解了應用程序內存使用的 hprof 輸出的相關部分。(hprof 工具在應用程序退出時,或者用 kill -3 或在 Windows 中按 Ctrl+Break 時生成使用分解。)注意兩次快照相比,Map.Entry、Task 和 int[] 對象有了顯著增加。

      請參閱 清單 3。

      清單 4 展示了 hprof 輸出的另一部分,給出了 Map.Entry 對象的分配點的調用堆棧信息。這個輸出告訴我們哪些調用鏈生成了 Map.Entry 對象,并帶有一些程序分析,找出內存泄漏來源一般來說是相當容易的。

      清單 4. HPROF 輸出,顯示 Map.Entry 對象的分配點

    TRACE 300446:
     java.util.HashMap$Entry.<init>(<Unknown Source>:Unknown line)
     java.util.HashMap.addEntry(<Unknown Source>:Unknown line)
     java.util.HashMap.put(<Unknown Source>:Unknown line)
     java.util.Collections$SynchronizedMap.put(<Unknown Source>:Unknown line)
     com.quiotix.dummy.MapLeaker.newTask(MapLeaker.java:48)
     com.quiotix.dummy.MapLeaker.main(MapLeaker.java:64)

      弱引用來救援了

      SocketManager 的問題是 Socket-User 映射的生命周期應當與 Socket 的生命周期相匹配,但是語言沒有提供任何容易的方法實施這項規則。這使得程序不得不使用人工內存管理的老技術。幸運的是,從 JDK 1.2 開始,垃圾收集器提供了一種聲明這種對象生命周期依賴性的方法,這樣垃圾收集器就可以幫助我們防止這種內存泄漏 —— 利用弱引用。

      弱引用是對一個對象(稱為 referent)的引用的持有者。使用弱引用后,可以維持對 referent 的引用,而不會阻止它被垃圾收集。當垃圾收集器跟蹤堆的時候,如果對一個對象的引用只有弱引用,那么這個 referent 就會成為垃圾收集的候選對象,就像沒有任何剩余的引用一樣,而且所有剩余的弱引用都被清除。(只有弱引用的對象稱為弱可及(weakly reachable)。)

      WeakReference 的 referent 是在構造時設置的,在沒有被清除之前,可以用 get() 獲取它的值。如果弱引用被清除了(不管是 referent 已經被垃圾收集了,還是有人調用了 WeakReference.clear()),get() 會返回 null。相應地,在使用其結果之前,應當總是檢查 get() 是否返回一個非 null 值,因為 referent 最終總是會被垃圾收集的。

      用一個普通的(強)引用拷貝一個對象引用時,限制 referent 的生命周期至少與被拷貝的引用的生命周期一樣長。如果不小心,那么它可能就與程序的生命周期一樣 —— 如果將一個對象放入一個全局集合中的話。另一方面,在創建對一個對象的弱引用時,完全沒有擴展 referent 的生命周期,只是在對象仍然存活的時候,保持另一種到達它的方法。

      弱引用對于構造弱集合最有用,如那些在應用程序的其余部分使用對象期間存儲關于這些對象的元數據的集合 —— 這就是 SocketManager 類所要做的工作。因為這是弱引用最常見的用法,WeakHashMap 也被添加到 JDK 1.2 的類庫中,它對鍵(而不是對值)使用弱引用。如果在一個普通 HashMap 中用一個對象作為鍵,那么這個對象在映射從 Map 中刪除之前不能被回收,WeakHashMap 使您可以用一個對象作為 Map 鍵,同時不會阻止這個對象被垃圾收集。清單 5 給出了 WeakHashMap 的 get() 方法的一種可能實現,它展示了弱引用的使用:

      清單 5. WeakReference.get() 的一種可能實現

    public class WeakHashMap<K,V> implements Map<K,V> {

        private static class Entry<K,V> extends WeakReference<K>
          implements Map.Entry<K,V> {
            private V value;
            private final int hash;
            private Entry<K,V> next;
            ...
        }

        public V get(Object key) {
            int hash = getHash(key);
            Entry<K,V> e = getChain(hash);
            while (e != null) {
                K eKey= e.get();
                if (e.hash == hash && (key == eKey || key.equals(eKey)))
                    return e.value;
                e = e.next;
            }
            return null;
        }

      調用 WeakReference.get() 時,它返回一個對 referent 的強引用(如果它仍然存活的話),因此不需要擔心映射在 while 循環體中消失,因為強引用會防止它被垃圾收集。WeakHashMap 的實現展示了弱引用的一種常見用法 —— 一些內部對象擴展 WeakReference。其原因在下面一節討論引用隊列時會得到解釋。

      在向 WeakHashMap 中添加映射時,請記住映射可能會在以后“脫離”,因為鍵被垃圾收集了。在這種情況下,get() 返回 null,這使得測試 get() 的返回值是否為 null 變得比平時更重要了。

      用 WeakHashMap 堵住泄漏

       在 SocketManager 中防止泄漏很容易,只要用 WeakHashMap 代替 HashMap 就行了,如清單 6 所示。(如果 SocketManager 需要線程安全,那么可以用 Collections.synchronizedMap() 包裝 WeakHashMap)。當映射的生命周期必須與鍵的生命周期聯系在一起時,可以使用這種方法。不過,應當小心不濫用這種技術,大多數時候還是應當使用 普通的 HashMap 作為 Map 的實現。

      清單 6. 用 WeakHashMap 修復 SocketManager

    public class SocketManager {
        private Map<Socket,User> m = new WeakHashMap<Socket,User>();
       
        public void setUser(Socket s, User u) {
            m.put(s, u);
        }
        public User getUser(Socket s) {
            return m.get(s);
        }
    }

      引用隊列

      WeakHashMap 用弱引用承載映射鍵,這使得應用程序不再使用鍵對象時它們可以被垃圾收集,get() 實現可以根據 WeakReference.get() 是否返回 null 來區分死的映射和活的映射。但是這只是防止 Map 的內存消耗在應用程序的生命周期中不斷增加所需要做的工作的一半,還需要做一些工作以便在鍵對象被收集后從 Map 中刪除死項。否則,Map 會充滿對應于死鍵的項。雖然這對于應用程序是不可見的,但是它仍然會造成應用程序耗盡內存,因為即使鍵被收集了,Map.Entry 和值對象也不會被收集。

      可以通過周期性地掃描 Map,對每一個弱引用調用 get(),并在 get() 返回 null 時刪除那個映射而消除死映射。但是如果 Map 有許多活的項,那么這種方法的效率很低。如果有一種方法可以在弱引用的 referent 被垃圾收集時發出通知就好了,這就是引用隊列 的作用。

      引用隊列是垃圾收集器向應用程序返回關于對象生命周期的信息的主要方法。弱引用有兩個構造函數:一個只取 referent 作為參數,另一個還取引用隊列作為參數。如果用關聯的引用隊列創建弱引用,在 referent 成為 GC 候選對象時,這個引用對象(不是 referent)就在引用清除后加入 到引用隊列中。之后,應用程序從引用隊列提取引用并了解到它的 referent 已被收集,因此可以進行相應的清理活動,如去掉已不在弱集合中的對象的項。(引用隊列提供了與 BlockingQueue 同樣的出列模式 —— polled、timed blocking 和 untimed blocking。)

      WeakHashMap 有一個名為 expungeStaleEntries() 的私有方法,大多數 Map 操作中會調用它,它去掉引用隊列中所有失效的引用,并刪除關聯的映射。清單 7 展示了 expungeStaleEntries() 的一種可能實現。用于存儲鍵-值映射的 Entry 類型擴展了 WeakReference,因此當 expungeStaleEntries() 要求下一個失效的弱引用時,它得到一個 Entry。用引用隊列代替定期掃描內容的方法來清理 Map 更有效,因為清理過程不會觸及活的項,只有在有實際加入隊列的引用時它才工作。

      清單 7. WeakHashMap.expungeStaleEntries() 的可能實現

        private void expungeStaleEntries() {
     Entry<K,V> e;
            while ( (e = (Entry<K,V>) queue.poll()) != null) {
                int hash = e.hash;

                Entry<K,V> prev = getChain(hash);
                Entry<K,V> cur = prev;
                while (cur != null) {
                    Entry<K,V> next = cur.next;
                    if (cur == e) {
                        if (prev == e)
                            setChain(hash, next);
                        else
                            prev.next = next;
                        break;
                    }
                    prev = cur;
                    cur = next;
                }
            }
        }

      結束語

      弱引用和弱集合是對堆進行管理的強大工具,使得應用程序可以使用更復雜的可及性方案,而不只是由普通(強)引用所提供的“要么全部要么沒有”可及性。下個月,我們將分析與弱引用有關的軟引用,將分析在使用弱引用和軟引用時,垃圾收集器的行為。

    posted @ 2006-01-04 18:35 javaGrowing 閱讀(249) | 評論 (0)編輯 收藏

    作者:Sunil Patil

    翻譯:loryliu


    版權聲明:可以任意轉載,轉載時請務必以超鏈接形式標明文章原始出處和作者信息及本聲明
    作者:
    Sunil Patil;loryliu
    原文地址:
    http://www.onjava.com/pub/a/onjava/2004/11/10/ExtendingStruts.html
    中文地址:
    http://www.matrix.org.cn/resource/article/43/43857_Struts.html
    關鍵詞: extending Struts


    簡介

    我 見過許多項目開發者實現自己專有的MVC框架。這些開發者并不是因為想實現不同于Struts的某些功能,而是還沒有意識到怎么去擴展Struts。通過 開發自己的MVC框架,你可以掌控全局,但同時這也意味著你必須付出很大的代價;在項目計劃很緊的情況下也許根本就不可能實現。

    Struts不但功能強大也易于擴展。你可以通過三種方式來擴展Struts:

    1.PlugIn:在應用啟動或關閉時須執行某業務邏輯,創建你自己的PlugIn類

    2.RequestProcessor:在請求處理階段一個特定點欲執行某業務邏輯,創建你自己的RequestProcessor。例如:你想繼承RequestProcessor來檢查用戶登錄及在執行每個請求時他是否有權限執行某個動作。

    3.ActionServlet:在應用啟動或關閉或在請求處理階段欲執行某業務邏輯,繼承ActionServlet類。但是必須且只能在PligIn和RequestProcessor都不能滿足你的需求時候用。

    本文會列舉一個簡單的Struts應用來示范如何使用以上三種方式擴展Struts。在本文末尾資源區有每種方式的可下載樣例源代碼。Struts Validation 框架和 Tiles 框架是最成功兩個的Struts擴展例子。

    我是假設讀者已經熟悉Struts框架并知道怎樣使用它創建簡單的應用。如想了解更多有關Struts的資料請參見資源區。

    PlugIn

    根據Struts文檔,“PlugIn是一個須在應用啟動和關閉時需被通知的模塊定制資源或服務配置包”。這就是說,你可以創建一個類,它實現PlugIn的接口以便在應用啟動和關閉時做你想要的事。

    假 如創建了一個web應用,其中使用Hibernate做為持久化機制;當應用一啟動,就需初始化Hinernate,這樣在web應用接收到第一個請求 時,Hibernate已被配置完畢并待命。同時在應用關閉時要關閉Hibernate。跟著以下兩步可以實現Hibernate PlugIn的需求。

    1.創建一個實現PlugIn接口的類,如下:

    public class HibernatePlugIn implements PlugIn{
            private String configFile;
            // This method will be called at application shutdown time
            public void destroy() {
                    System.out.println("Entering HibernatePlugIn.destroy()");
                    //Put hibernate cleanup code here
                    System.out.println("Exiting HibernatePlugIn.destroy()");
            }
            //This method will be called at application startup time
            public void init(ActionServlet actionServlet, ModuleConfig config)
                    throws ServletException {
                    System.out.println("Entering HibernatePlugIn.init()");
                    System.out.println("Value of init parameter " +
                                        getConfigFile());
                    System.out.println("Exiting HibernatePlugIn.init()");
            }
            public String getConfigFile() {
                    return name;
            }
            public void setConfigFile(String string) {
                    configFile = string;
            }
    }


    實現PlugIn接口的類必須是實現以下兩個方法:
    init() 和destroy().。在應用啟動時init()被調用,關閉destroy()被調用。Struts允許你傳入初始參數給你的PlugIn類;為了傳 入參數你必須在PlugIn類里為每個參數創建一個類似JavaBean形式的setter方法。在HibernatePlugIn類里,欲傳入 configFile的名字而不是在應用里將它硬編碼進去

    2.在struts-condig.xml里面加入以下幾行告知Struts這個新的PlugIn

    <struts-config>
            ...
            <!-- Message Resources -->
            <message-resources parameter=
              "sample1.resources.ApplicationResources"/>

            <!-- Declare your plugins -->
            <plug-in className="com.sample.util.HibernatePlugIn">
                    <set-property property="configFile"
                       value="/hibernate.cfg.xml"/>
            </plug-in>
    </struts-config>

    ClassName 屬性是實現PlugIn接口類的全名。為每一個初始化傳入PlugIn類的初始化參數增加一個<set-property>元素。在這個例子 里,傳入config文檔的名稱,所以增加了一個config文檔路徑的<set-property>元素。

    Tiles和Validator框架都是利用PlugIn給初始化讀入配置文件。另外兩個你還可以在PlugIn類里做的事情是:

    假如應用依賴于某配置文件,那么可以在PlugIn類里檢查其可用性,假如配置文件不可用則拋出ServletException。這將導致ActionServlet不可用。

    PlugIn接口的init()方法是你改變ModuleConfig方法的最后機會,ModuleConfig方法是描述基于Struts模型靜態配置信息的集合。一旦PlugIn被處理完畢,Struts就會將ModuleCOnfig凍結起來。

    請求是如何被處理的

    ActionServlet 是Struts框架里唯一一個Servlet,它負責處理所有請求。它無論何時收到一個請求,都會首先試著為現有請求找到一個子應用。一旦子應用被找到, 它會為其生成一個RequestProcessor對象,并調用傳入HttpServletRequest和HttpServletResponse為參 數的process()方法。

    大部分請處理都是在RequestProcessor.process()發生的。Process()方法 是以模板方法(Template Method)的設計模式來實現的,其中有完成request處理的每個步驟的方法;所有這些方法都從process()方法順序調用。例如,尋找當前請 求的ActionForm類和檢查當前用戶是否有權限執行action mapping都有幾個單獨的方法。這給我們提供了極大的彈性空間。Struts的RequestProcessor對每個請求處理步驟都提供了默認的實 現方法。這意味著,你可以重寫你感興趣的方法,而其余剩下的保留默認實現。例如,Struts默認調用request.isUserInRole()檢查 用戶是否有權限執行當前的ActionMapping,但如果你需要從數據庫中查找,那么你要做的就是重寫processRoles()方法,并根據用戶 角色返回true 或 false。

    首先我們看一下process()方法的默認實現方式,然后我將解釋RequestProcessor類里的每個默認的方法,以便你決定要修改請求處理的哪一部分。

    public void process(HttpServletRequest request,
                            HttpServletResponse response)
        throws IOException, ServletException {
            // Wrap multipart requests with a special wrapper
            request = processMultipart(request);
            // Identify the path component we will
            // use to select a mapping
            String path = processPath(request, response);
            if (path == null) {
                return;
            }
            if (log.isDebugEnabled()) {
                log.debug("Processing a '" + request.getMethod() +
                          "' for path '" + path + "'");
            }
            // Select a Locale for the current user if requested
            processLocale(request, response);
            // Set the content type and no-caching headers
            // if requested
            processContent(request, response);
            processNoCache(request, response);
            // General purpose preprocessing hook
            if (!processPreprocess(request, response)) {
                return;
           }
            // Identify the mapping for this request
            ActionMapping mapping =
                processMapping(request, response, path);
            if (mapping == null) {
                return;
            }
            // Check for any role required to perform this action
            if (!processRoles(request, response, mapping)) {
                return;
            }
            // Process any ActionForm bean related to this request
            ActionForm form =
                processActionForm(request, response, mapping);
            processPopulate(request, response, form, mapping);
            if (!processValidate(request, response, form, mapping)) {
                return;
            }
            // Process a forward or include specified by this mapping
            if (!processForward(request, response, mapping)) {
                return;
            }
            if (!processInclude(request, response, mapping)) {
                return;
            }
            // Create or acquire the Action instance to
            // process this request
            Action action =
                processActionCreate(request, response, mapping);
            if (action == null) {
                return;
            }
            // Call the Action instance itself
            ActionForward forward =
                processActionPerform(request, response,
                                    action, form, mapping);
            // Process the returned ActionForward instance
            processForwardConfig(request, response, forward);
        }



    1、processMultipart(): 在 這個方法中,Struts讀取request以找出contentType是否為multipart/form-data。假如是,則解析并將其打包成一 個實現HttpServletRequest的包。當你成生一個放置數據的HTML FORM時,request的contentType默認是application/x-www-form-urlencoded。但是如果你的form 的input類型是FILE-type允許用戶上載文件,那么你必須把form的contentType改為multipart/form-data。如 這樣做,你永遠不能通過HttpServletRequest的getParameter()來讀取用戶提交的form值;你必須以 InputStream的形式讀取request,然后解析它得到值。

    2、processPath(): 在這個方法中,Struts將讀取request的URI以判斷用來得到ActionMapping元素的路徑。

    3、processLocale(): 在這個方法中,Struts將得到當前request的Locale;Locale假如被配置,將作為 org.apache.struts.action.LOCALE屬性的值被存入HttpSession。這個方法的附作用是HttpSession會被 創建。假如你不想此事發生,可將在struts-config.xml 文件里ControllerConfig的local屬性設置為false,如下:
    <controller>
            <set-property property="locale" value="false"/>
    </controller>


    4、processContent():通過調用response.setContentType()設置response的contentType。這個方法首先會試著的得到配置在struts-config.xml里的contentType。默認為text/html,重寫方法如下:
    <controller>
            <set-property property="contentType" value="text/plain"/>
    </controller>


    5、processNoCache():Struts將為每個response的設置以下三個header,假如已在struts 的config.xml將配置為no-cache。
    response.setHeader("Pragma", "No-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 1);


    假如你想設置為no-cache header,在struts-config.xml中加如以下幾行
    <controller>
            <set-property property="noCache" value="true"/>
    </controller>


    6、processPreprocess():這是一個一般意義的預處理hook,其可被子類重寫。在RequestProcessor里的實現什么都沒有做,總是返回true。如此方法返回false會中斷請求處理。

    7、processMapping():這個方法會利用path信息找到ActionMapping對象。ActionMapping對象在struts-config.xml file文件里表示為<action>
    <action path="/newcontact" type="com.sample.NewContactAction"
            name="newContactForm" scope="request">
            <forward name="sucess" path="/sucessPage.do"/>
            <forward name="failure" path="/failurePage.do"/>
    </action>


    ActionMapping元素包含了如Action類的名稱及在請求中用到的ActionForm的信息,另外還有配置在當前ActionMapping的里的ActionForwards信息。

    8、processRoles(): Struts的web 應用安全提供了一個認證機制。這就是說,一旦用戶登錄到容器,Struts的processRoles()方法通過調用request.isUserInRole()可以檢查他是否有權限執行給定的ActionMapping。
            <action path="/addUser" roles="administrator"/>

    假如你有一個AddUserAction,限制只有administrator權限的用戶才能新添加用戶。你所要做的就是在AddUserAction 的action元素里添加一個值為administrator的role屬性。

    9、processActionForm():每個ActionMapping都有一個與它關聯的ActionForm類。struts在處理ActionMapping時,他會從<action>里name屬性找到相關的ActionForm類的值。
    <form-bean name="newContactForm" 
               type="org.apache.struts.action.DynaActionForm">
                    <form-property name="firstName"
                              type="java.lang.String"/>
                    <form-property name="lastName"
                              type="java.lang.String"/>
    </form-bean>


    在這個例子里,首先會檢查org.apache.struts.action.DynaActionForm類的對象是否在request 范圍內。如是,則使用它,否則創建一個新的對象并在request范圍內設置它。

    10、processPopulate()::在這個方法里,Struts將匹配的request parameters值填入ActionForm類的實例變量中。

    11、processValidate():Struts將調用ActionForm的validate()方法。假如validate()返回ActionErrors,Struts將用戶轉到由<action>里的input屬性標示的頁面。

    12、processForward() and processInclude():在這兩個方法里,Struts檢查<action>元素的forward和include屬性的值,假如有配置,則把forward和include 請求放在配置的頁面內。
    <action forward="/Login.jsp" path="/loginInput"/>
            <action include="/Login.jsp" path="/loginInput"/>


    你 可以從他們的名字看出其不同之處。processForward()調用RequestDispatcher.forward(),, processInclude()調用RequestDispatcher.include()。假如你同時配置了orward 和include 屬性,Struts總會調用forward,因為forward,是首先被處理的。

    13、processActionCreate():這個方法從<action>的type屬性得到Action類名,并創建返回它的實例。在這里例子中struts將創建一個com.sample.NewContactAction類的實例。

    14、processActionPerform():這個方法調用Action 類的execute()方法,其中有你寫入的業務邏輯。

    15、processForwardConfig():Action類的execute()將會返回一個ActionForward類型的對象,指出哪一頁面將展示給用戶。因此Struts將為這個頁面創建RequestDispatchet,然后再調用RequestDispatcher.forward()方法。

    以 上列出的方法解釋了RequestProcessor在請求處理的每步默認實現及各個步驟執行的順序。正如你所見,RequestProcessor很有 彈性,它允許你通過設置<controller>里的屬性來配置它。例如,假如你的應用將生成XML內容而不是HTML,你可以通過設置 controller的某個屬性來通知Struts。

    創建你自己的RequestProcessor

    從 以上內容我們已經明白了RequestProcessor的默認實現是怎樣工作的,現在我將通過創建你自己的RequestProcessor.展示一個 怎樣自定義RequestProcessor的例子。為了演示創建一個自定義RequestProcessor,我將修改例子實現以下連個業務需求:

    我們要創建一個ContactImageAction類,它將生成images而不是一般的HTMl頁面

    在處理這個請求之前,將通過檢查session里的userName屬性來確認用戶是否登錄。假如此屬性沒有被找到,則將用戶轉到登錄頁面。


    分兩步來實現以上連個業務需求。
    創建你自己的CustomRequestProcessor類,它將繼承RequestProcessor類,如下:

    public class CustomRequestProcessor
        extends RequestProcessor {
            protected boolean processPreprocess (
                HttpServletRequest request,
                HttpServletResponse response) {
                HttpSession session = request.getSession(false);
            //If user is trying to access login page
            // then don't check
            if( request.getServletPath().equals("/loginInput.do")
                || request.getServletPath().equals("/login.do") )
                return true;
            //Check if userName attribute is there is session.
            //If so, it means user has allready logged in
            if( session != null &&
            session.getAttribute("userName") != null)
                return true;
            else{
                try{
                    //If no redirect user to login Page
                    request.getRequestDispatcher
                        ("/Login.jsp").forward(request,response);
                }catch(Exception ex){
                }
            }
            return false;
        }

        protected void processContent(HttpServletRequest request,
                    HttpServletResponse response) {
                //Check if user is requesting ContactImageAction
                // if yes then set image/gif as content type
                if( request.getServletPath().equals("/contactimage.do")){
                    response.setContentType("image/gif");
                    return;
                }
            super.processContent(request, response);
        }
    }


    在CustomRequestProcessor 類的processPreprocess方法里,檢查session的userName屬性,假如沒有找到,將用戶轉到登錄頁面。

    對 于產生images作為ContactImageAction類的輸出,必須要重寫processContent方法。首先檢查其request是否請求 /contactimage路徑,如是則設置contentType為image/gif;否則為text/html。

    加入以下幾行代碼到sruts-config.xml文件里的<action-mapping>后面,告知Struts ,CustomRequestProcessor應該被用作RequestProcessor類

    <controller>
            <set-property  property="processorClass"
            value="com.sample.util.CustomRequestProcessor"/>
    </controller>


    請 注意,假如你只是很少生成contentType不是text/html輸出的Action類,重寫processContent()就沒有問題。如不是 這種情況,你必須創建一個Struts子系統來處理生成image  Action的請求并設置contentType為image/gif

    Title框架使用自己的RequestProcessor來裝飾Struts生成的輸出。

    ActionServlet

    假如你仔細研究Struts web應用的web.xml文件,它看上去像這樣:
    <web-app >
            <servlet>
                <servlet-name>action=</servlet-name>
                <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
                <!-- All your init-params go here-->
            </servlet>
            <servlet-mapping>
                <servlet-name>action</servlet-name>
                <url-pattern>*.do</url-pattern>
            </servlet-mapping>
    </web-app >


    這 就是說,ActionServlet負責處理所有發向Struts的請求。你可以創建ActionServlet的一個子類,假如你想在應用啟動和關閉時 或每次請求時做某些事情。但是你必須在繼承ActionServlet類前創建PlugIn 或 RequestProcessor。在Servlet 1.1前,Title框架是基于繼承ActionServlet類來裝飾一個生成的response。但從1.1開始,就使用 TilesRequestProcessor類。

    結論

    開發你自己的MVC模型是一個很大的決心——你必須考慮開發和維護代碼的時間和資源。Struts是一個功能強大且穩定的框架,你可以修改它以使其滿足你大部分的業務需求。

    另一方面,也不要輕易地決定擴展Struts。假如你在RequestProcessor里放入一些低效率的代碼,這些代碼將在每次請求時執行并大大地降低整個應用的效率。當然總有創建你自己的MVC框架比擴展Struts更好的情況。

    資源
    下載本文源碼:[下載文件]
    Struts主頁
    "Jakarta Struts框架介紹"
    "學習Jakarta Struts 1.1"


    Sunil Pail已從事J2EE四年,現今與IBM實驗室合作。
    posted @ 2005-12-30 09:48 javaGrowing 閱讀(343) | 評論 (0)編輯 收藏

    關鍵字:beanutils、dbutils、JDBC 數據庫
    摘要:本文簡單介紹了Jakarta Commons旗下beanutils、dbutils在基于JDBC API數據庫存取操作中的運用。
         雖然現在出現了很多ORM框架,可是還是有很多朋友也許還在使用JDBC,就像我現在一樣,除了學習的時候在使用Hibernate、Spring類似這些優秀的框架,工作時一直都在使用JDBC。本文就簡單介紹一下利用Jakarta Commons旗下beanutils、dbutils簡化JDBC數據庫操作,以拋磚引玉,希望對像我一樣在使用JDBC的朋友有所幫助。
        下面就分兩部分簡單介紹beanutils、dbutils在基于JDBC API數據庫存取操作中的運用。第一部分顯介紹beanutils在JDBC數據庫存取操作中的運用,第二部分介紹dbutils在JDBC數據庫存取操作中的運用,最后看看他們的優缺點,談談本人在項目運用過程中對他們的一點心得體會,僅供參考,其中有錯誤的地方希望大蝦不吝賜教,大家多多交流共同進步。
    一、Jakarta Commons beanutils
           Beanutils是操作Bean的銳利武器,其提過的BeanUtils工具類可以簡單方便的讀取或設置Bean的屬性,利用Dyna系列,還可以在運行期創建Bean,符合懶人的習慣,正如LazyDynaBean,LazyDynaClass一樣,呵呵。這些用法已經有很多文章提及,也可以參考apache的官方文檔。
    對于直接利用JDBC API訪問數據庫時(這里針對的是返回結果集ResultSet的查詢select),大多數都是采用兩種方式,一種是取出返回的結果集的數據存于Map中,另一種方式是Bean里。針對第二種方式,Beanutils里提供了ResultSetDynaClass結合DynaBean以及RowSetDynaClass結合DynaBean來簡化操作。下面用以個簡單的例子展示一下beanutils的這兩個類在JDBC數據庫操作中的運用。
    請在本機建立數據庫publish,我用的是MySQL,在publish數據庫中建立表book,腳本如下:
    CREATE TABLE book(
      id int(11) NOT NULL auto_increment,
      title varchar(50) character set latin1 NOT NULL,
      authors varchar(50) character set latin1 default NULL, 
      PRIMARY KEY  (id)

             然后用你喜歡的編輯器建立一個類BeanutilsJDBCTest,我們先用ResultSetDynaClass來處理,然后再用RowSetDynaClass來實現同樣的類,之后看看他們之間有什么不同,用ResultSetDynaClass處理的源代碼如下所示:
             然后用你喜歡的編輯器建立一個類BeanutilsJDBCTest,我們先用ResultSetDynaClass來處理,然后再用RowSetDynaClass來實現同樣的類,之后看看他們之間有什么不同,用ResultSetDynaClass處理的源代碼如下所示:
    package cn.qtone.test;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.ResultSet;
    import java.sql.Statement;
    import java.util.Iterator; 
    import org.apache.commons.beanutils.DynaBean;
    import org.apache.commons.beanutils.PropertyUtils;
    import org.apache.commons.beanutils.ResultSetDynaClass; 
    public class BeanutilsJDBCTest{
           public static void main(String[] args) {
                  Connection con = null;
                  Statement st = null;
                  ResultSet rs = null;
                  try {
                         Class.forName("com.mysql.jdbc.Driver");
                         String url = "jdbc:mysql://127.0.0.1:3306/publish?useUnicode=true&characterEncoding=GBK";
                         con = DriverManager.getConnection(url, "root", "hyys");
                         st = con.createStatement();
                         rs = st.executeQuery("select * from book");
                         ResultSetDynaClass rsDynaClass = new ResultSetDynaClass(rs);
                         Iterator itr = rsDynaClass.iterator();
                         System.out.println("title-------------authors");
                         while (itr.hasNext()) {
                                DynaBean dBean = (DynaBean) itr.next();
                                System.out.println(PropertyUtils.getSimpleProperty(dBean,"title")
                                              + "-------------"+ PropertyUtils.getSimpleProperty(dBean, "authors"));
                         }
                  } catch (Exception e) {
                         e.printStackTrace();
                  } finally {
                         try {
                                if (rs != null) {
                                       rs.close();
                                }
                                if (st != null) {
                                       st.close();
                                }
                                if (con != null) {
                                       con.close();
                                }
                         } catch (Exception e) {
                                e.printStackTrace();
                         }
                  }
           }

    用RowSetDynaClass處理的源代碼如下所示: 
    package cn.qtone.test; 
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.ResultSet;
    import java.sql.Statement;
    import java.util.Iterator;
    import java.util.List; 
    import org.apache.commons.beanutils.DynaBean;
    import org.apache.commons.beanutils.PropertyUtils;
    import org.apache.commons.beanutils.RowSetDynaClass; 
    public class BeanutilsJDBCTest{
           public static void main(String[] args) {
                  List rsDynaClass = rsTest();
                  System.out.println("title ------------- authors ");
                  Iterator itr = rsDynaClass.iterator();
                  while (itr.hasNext()) {
                         DynaBean dBean = (DynaBean) itr.next();
                         try {
                                System.out.println(PropertyUtils.getSimpleProperty(dBean,"name")
                                              + "-------------"+ PropertyUtils.getSimpleProperty(dBean, "mobile"));
                         } catch (Exception e) {
                                // TODO 自動生成 catch 塊
                                e.printStackTrace();
                         }
                  }
           } 
           private static List rsTest() {
                  Connection con = null;
                  Statement st = null;
                  ResultSet rs = null;
                  try {
                         Class.forName("com.mysql.jdbc.Driver");
                         String url = "jdbc:mysql://127.0.0.1:3306/publish?useUnicode=true&characterEncoding=GBK";
                         con = DriverManager.getConnection(url, "root", "hyys");
                         st = con.createStatement();
                         rs = st.executeQuery("select * from book");
                         RowSetDynaClass rsdc = new RowSetDynaClass(rs);
                         return rsdc.getRows();
                  } catch (Exception e) {
                         e.printStackTrace();
                  } finally {
                         try {
                                if (rs != null) {
                                       rs.close();
                                }
                                if (st != null) {
                                       st.close();
                                }
                                if (con != null) {
                                       con.close();
                                }
                         } catch (Exception e) {
                                e.printStackTrace();
                         }
                  }
                  return null;
           }

    這兩個方法輸出的結果應該是一樣的。但是很顯然第二種方式比第一種方式要好,它把數據訪問部分抽取出來放到一個方法中,顯得簡單清晰。
    其實在利用ResultSetDynaClass時,必須在ResultSet等數據庫資源關閉之前,處理好那些數據,你不能在資源關閉之后使用DynaBean,否則就會拋出異常,異常就是說不能在ResultSet之后存取數據(具體的異常名我也忘了),當然你也可以采用以前的方式一個一個的把數據放到Map里,如果你一定要那樣做,建議還是別用Beanutils,因為這沒帶給你什么好處。總之利用ResultSetDynaClass你的程序的擴展性非常部好。
    從第二中方式可以看出,利用RowSetDynaClass可以很好的解決上述ResultSetDynaClass遇到的問題,RowSetDynaClass的getRows()方法,把每一行封裝在一個DynaBean對象里,然后,把說有的行放到一個List里,之后你就可以對返回的List里的每一個DynaBean進行處理,此外對于DynaBean你還可以采用標準的get/set方式處理,當然你也可以用PropertyUtils. getSimpleProperty(Object bean, String name)進行處理。
    從上面的分析中,你應該可以決定你應該使用ResultSetDynaClass還是RowSetDynaClass了。

    posted @ 2005-12-29 22:41 javaGrowing 閱讀(288) | 評論 (0)編輯 收藏

    僅列出標題
    共19頁: First 上一頁 11 12 13 14 15 16 17 18 19 下一頁 
    主站蜘蛛池模板: 亚洲中文字幕无码久久精品1| 亚洲视频在线免费播放| 99精品视频在线观看免费| 久久水蜜桃亚洲av无码精品麻豆| 久久精品a一国产成人免费网站| 黄色片网站在线免费观看| 久久精品国产亚洲AV麻豆~| 四虎成人免费观看在线网址| 精品国产污污免费网站入口在线| 亚洲精品人成电影网| 国产一级淫片a视频免费观看| 无码人妻久久一区二区三区免费| 亚洲日本在线电影| 黑人精品videos亚洲人| 在线v片免费观看视频| 中文字幕在线视频免费| 亚洲最大无码中文字幕| 亚洲一区二区女搞男| 一个人在线观看视频免费| a级毛片黄免费a级毛片| 亚洲熟妇av午夜无码不卡| 亚洲AV永久无码精品水牛影视| 日韩高清免费观看| 最好看的中文字幕2019免费| 一区二区三区在线免费 | 亚洲精品无码MV在线观看| 免费a级毛片高清视频不卡| 一个人看www免费高清字幕| 久久狠狠爱亚洲综合影院 | 久久青青草原亚洲av无码app| 国产成人毛片亚洲精品| 毛片免费全部播放一级| 99精品视频在线观看免费专区| 少妇亚洲免费精品| 亚洲码欧美码一区二区三区| 亚洲综合男人的天堂色婷婷| 日韩亚洲欧洲在线com91tv| 亚洲成a人片在线播放| 色窝窝免费一区二区三区 | 亚洲综合伊人久久大杳蕉| 国产一区二区三区在线观看免费|