2010年9月28號---JavaEE學習之狀態對象:數據庫的替代者(轉載自J道)

轉載連接:http://www.jdon.com/artichect/state.htm

板橋里人 http://www.jdon.com 2006/1/2(轉載請保留)


  如果你經驗和經歷中沒有狀態這個概念,極端地說:可能你的Java系統經驗還未積累到一定程度,狀態是每個Java程序員深入Java系統後必然碰到的問題。  這是一個實戰中非常重要但是容易被忽視的概念,說它重要,是因爲它比數據庫重要;說它容易被忽視也是同樣的原因,它經常被數據庫概念替代。

  本文我想試圖表達的是:狀態分兩種:活動的狀態對象和持久化的狀態。而數據庫中的數據只是狀態的一種持久化結果,而Java系統 運行時,我們更多的可能是和一種活動的狀態打交道,這種活動的狀態存在內存中,而不是持久化到硬盤上,當然,需要時你可以通過數據庫/文件持久化到硬盤上。

  但是,如果你以數據庫數據替代狀態,那麼就可能導致數據庫的頻繁訪問,而且 你的系統會變成一個非對象化的、緊耦合、到處是分散數據塊的糟糕系統。這樣的系統並不比傳統的兩層結構好到哪裏!也不會比Jsp裏嵌入Java代碼僞三層系統高明到什麼地方。

什麼是狀態?

  只要有對象就可能有狀態,任何一個對象活動時,都有自己的狀態屬性,類的 字段屬性極有可能成爲狀態,我們現在經常使用的Domain model其實就是一種 包含狀態的對象,如果你對狀態沒有深入掌握,就不可能真正掌握對象系統特點,或者是Domain Model的執行情況。

  對於初學者,經常會疑問:我是將數據放在HttpSession中還是Request中,這裏 其實已經開始接觸狀態,一旦你接觸狀態,你就要開始小心,因爲你可能會將內存泄漏的惡魔導引進來。

  內存泄漏的惡魔爆發時刻取決於你狀態的生存週期和系統併發訪問量。

  狀態的生存週期也就是包含這個狀態的對象的生命週期,在簡單系統中,我們只 需要通過new創建對象,然後它的消亡就會依靠JVM垃圾回收機制回收,但是事情會這麼簡單嗎?

  狀態的危險還會發生在多線程環境下,當多個線程對同一個內存中狀態寫操作時,這時怎麼辦?如果這個狀態持久化在數據庫中,我們會依賴數據庫提供的強大事務機制防止這種併發死鎖,但是如果是在內存中,你就很難辦,因此,我們就儘量避免發生這種多線程同時訪問一個狀態的現象,而Singleton單例模式極容易發生這種現象,因此實踐中,單例模式是J2EE開發中需要避免的,相關帖子討論見:
http://www.jdon.com/jive/article.jsp?forum=91&thread=17578

  我們接觸的Web容器或Jsp/Servlet本質就是一個多線程,這也是很多初學者不知道的, 因爲多線程編程是複雜或困難的,因此纔有jsp/Servlet這樣的上層封裝,但是我們使用他們
時,實際在進行多線程編程。

  生命週期和多線程併發使得我們簡單的面向對象系統變得異常複雜和難以掌握起來。下面我從這個兩個角度,給出兩種模式思維解決之道。

生命週期(Scope)

  生命週期(Scope)就是指狀態的活動週期,狀態對象是什麼時候被創建;然後什麼時候被銷燬,很顯然,如果狀態對象還沒有被創建或已經被銷燬,你再訪問這個狀態對象可能失敗,而狀態的生命週期控制是可能散落在運行程序的各個地方,如果不象狀態模式那樣進行統一控制,有可能整個系統是危機四伏的。

  狀態的生命週期其實就是對象生命週期,更加細化地說:是Domain Model這個對象的生命週期。這在一個以領域模型爲驅動的設計概念中不可迴避的課題,而領域模型實戰的複雜性就複雜在此。

  狀態的生命週期在J2EE中目前有三種:Request/Session和Application,Request是每個客戶端發出的一次請求,這是J2EE系統中最基本的事件激活單元, 當服務器端推出一個頁面到客戶端時,意味着這個Request的結束。那麼如果我們的狀態保存在Request中,意味着在request結束之前,這個請求經歷的任何一個環節都可以對這個狀態(對象)進行操作。(掌握這個原理,對於你學習Struts和JSF很有幫助)

  如果是Session,則一直和該客戶端有關,只要是該客戶端發出的每次request的任何環節都可以對這個狀態(對象)進行操作。

  如果是Application,則意味着這個狀態是當前Web項目的全局狀態。

  這三種狀態形式都是以將狀態保存在內存中形式存在的,是和持久化狀態相對的。是一種內存活動狀態。

  生命週期的選取當然是越短越好,這樣,這個狀態對象就可以被自動銷燬,從而避免了
大訪問量下的內存泄漏,但是在大訪問量下,對象頻繁創建和銷燬是耗費性能的。

  那麼,我們可能經常使用HttpSession來保存狀態,這時你極有可能造成內存泄漏,我經常在Jdon論壇上看到將很多數據庫數據暫時保存在HttpSession中想法,這是相當危險的,因爲一旦併發用戶很多,相當多的HttpSession包含了狀態,而狀態中有可能有更多其他引用,因此內存很快會爆滿,或者垃圾回收機制頻繁啓動,造成應用系統運行暫停或緩慢。

  當你將狀態放入HttpSession時,難道沒有考慮將其手工消除嗎?你要知道所有Web容器(Tomcat/Weblogic等)都不會自動替你清除那些你可能不用的狀態對象啊。如果每個人只管新增元素,不管重整或管理,這個系統能不變得混亂嗎?代碼上這種現象我們是通過Refactoring等結構/行爲模式來解決,那麼在運行時的狀態管理呢?

  狀態管理模式或者說對象管理模式正是解決這種問題的。

   按照該模式,你必須手工自己管理放在HttpSession的狀態,比如你爲每個HttpSession
設立一個狀態容器最大尺寸,當超過這個尺寸時,你需要將不用的狀態從容器去除, 但是如果這個客戶端在Session失效期內又來訪問這個狀態怎麼辦?那麼你可能需要先臨時將狀態序列化保存到硬盤上,等Session失效期到達後再真正刪除。

  是不是覺得很麻煩?
  捷徑是有:
  1. 儘量少使用HttpSession保存狀態,這對集羣環境也是有利的,見該貼討論:
http://www.jdon.com/jive/article.jsp?forum=121&thread=22282
那麼這些狀態放在哪裏?使用Application的緩存中,

  2. 使用狀態管理中間件,目前有幾個選擇:EJB的有態Bean;NanoContainer之類狀態相關的微容器。那麼Spring可以嗎?目前沒有發現有該功能,甚至在Spring容器內無法直接使用Session性質的狀態,只能通過線程級別的ThreadLocal來實現(對不起,你又要開始回到遠古的彙編線程時代了);而Jdon框架則可以。

  下面我們談談Application的狀態,在這個範圍內,一個對象狀態可以被多個用戶反覆訪問,在這個級別,狀態類似數據庫中數據,因爲可以使用數據庫來替代這個級別的狀態,所以將狀態放入緩存這個深層次技術被大多數初學者忽視了,甚至產生了對數據庫依賴心理。

緩存中的狀態

  雖然我們將狀態保存在Application中,但是我們不可避免還是遇到Session同樣的狀態管理問題,這個問題所幸的是有專門緩存中間件解決了,當然,在一個多服務器集羣系統,如果一個客戶端在一個服務器中存放了狀態,那麼能否在另外一個服務器的內存中訪問到呢?回答是肯定的,前提是你必須使用分佈式緩存系統。

  目前分佈式緩存系統是靠EJB服務器完成,當JBoss 5在2006變成完全解耦、可肢解時,
我們就可以使用原本只支持EJB的JBoss分佈式緩存系統來支持我們的普通JavaBeans了(POJO)。這其中目前可能會花費一些力氣,因爲還沒有一個統一的POJO構件接口標準,我相信以後
可能會有。

  如果你不想花費力氣,而且可能就只是一臺服務器,可以通過雙核芯片提升性能,那麼單態緩存如果實現?很簡單,使用一個緩存產品如OsCache等,將其設定保存在 Application中,或者在web.xml中進行一下簡單的配置即可。

  但是,這時你可能碰到另外一個問題:狀態的唯一標識,如何通過唯一標識從緩存中那麼
多對象狀態中取出你要的那一個呢?比較瑣碎。

  有沒有一個框架幫助你省卻這些麻煩,當然推薦Jdon Framework,只要將包含狀態的類(主要是Domain Model)繼承特定的類或接口(接口在1.4版本實現)即可,這個類的對象運行時就會被緩存或從緩存中讀取,再也無需你照料緩存了,就這麼簡單。

  當然,Jdon Framework的底層緩存器是可以被替代,使用你喜歡的緩存產品,因爲jdon 
Framework是基於Ioc設計,構件之間是完全解耦、可徹底肢解,能夠通過配置替代和更換的。
如果你不明白這個道理,需要好好研究一下Ioc模式帶給我們革命性的新變化。

  從以上也可以看出:java複雜性還在於我們需要在編碼時,卻要想象其運行時的情形。而這種翻譯聯想沒有深厚的實踐功底,是很難順利完成的。

狀態管理中間件

  自從J2EE開闢中間件時代以來,就有相當多的高級中間件提供與具體應用無關的通用功能,狀態管理中間件很早就有之,EJB的有態Session Bean是一個代表。

  一箇中間件不但要有良好的松耦合設計,我們暫時稱爲靜態設計;更要有優秀的動態設計,例如狀態管理就屬於一種動態設計。

  當然,如果你比較謙虛,不但要選擇一些靜態設計很好的框架或中間件;而且還要依賴一些擁有良好的動態運行管理的中間件。

  EJB無論是EJB1.X/EJB2.X/EJB3.X.在狀態管理上要更加優秀,當然EJB3.X又吸收了優秀的靜態設計概念,但是因爲需要有一個具體服務器實現過程,這個過程中存在一些陷阱,如In-Box問題等。

  Spring無疑是一個靜態設計非常優秀框架,它一直在AOP上孜孜不倦,力圖探索一條從AOP角度進行動態運行管理干預捷徑,相信會有驚人結果,當然,這種細粒度的AOP需要實踐檢驗,當然如果整入JDK 6.0就更好。

  而Jdon Framework則試圖在目前兩者之間尋求了一個平衡,既有Ioc/AOP優秀的靜態設計,雖然在AOP上不及Spring前衛;但提供了切實Session和Cache狀態管理;

  如果你不需要EJB的分佈式多服務器集羣功能;又不是AOP的超級粉絲,無疑使用Jdon Framework之類的框架無疑是簡化方便的。

狀態設計的難點

  最後,我不得不重申,並不是有了良好的狀態管理框架就可以高枕無憂了,狀態的設計其實是我們每個項目必須面臨的可變課題,如果狀態複雜了可以使用狀態模式對付,可惜往往狀態不夠複雜。

  一個對象本身屬性和狀態是應該耦合在一起,還是進行分離,屬性和狀態沒有明顯的涇渭分明的界限,我們舉一個例子:

  論壇Forum這個對象,它有一些字段屬性,如論壇名稱、論壇描述,還有其他一些相關屬性:如該論壇的最新發帖;該論壇的發貼量,後兩者好像也是論壇字段,但是他們可能經常變化的,應該屬於狀態,那麼狀態和Forum這個主體對象是什麼關係?是將該論壇的最新發帖和該論壇的發貼量兩個字段併入Forum這個Domain Model中,還是應該單獨建立一個狀態對象?如果進行分離,分離的依據是什麼?

  當然,這裏分離的依據是因爲對象的生存週期不同。對於我們熟悉的課題,我們能夠馬上分辨出其中的生存週期,如果是不熟悉領域的建模呢?

  所以,大家已經明白:狀態設計的難點是:如何粒度細化地創建模型對象;然後分辨出其中動態的狀態性質。這是域建模實戰中一個難點。

  很多人問我:你提倡的域建模、設計模式和框架是什麼意思?爲什麼說他們是Java開發設計的三件寶呢?或者說三個典型知識點呢?我想通過本篇我已經通過狀態這個概念稍微解釋了域建模的一些特點。

  當前,MDA中的四色原型模式Archetype將幫助我們更好地分辨出類的屬性、狀態和行爲,這是一場帶來以後十年的軟件革命,詳情請看下面討論

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章