天天看點

狀态對象:資料庫的替代者

  這是一個實戰中非常重要但是容易被忽視的概念,說它重要,是因為它比資料庫重要;說它容易被忽視也是同樣的原因,它經常被資料庫概念替代。

  如果你經驗和經曆中沒有狀态這個概念,極端地說:可能你的java系統經驗還未積累到一定程度,狀态是每個java程式員深入java系統後必然碰到的問題。

  本文我想試圖表達的是:狀态分兩種:活動的狀态對象和持久化的狀态。而資料庫中的資料隻是狀态的一種持久化結果,而java系統 運作時,我們更多的可能是和一種活動的狀态打交道,這種活動的狀态存在記憶體中,而不是持久化到硬碟上,當然,需要時你可以通過資料庫/檔案持久化到硬碟上。

  但是,如果你以資料庫資料替代狀态,那麼就可能導緻資料庫的頻繁通路,而且 你的系統會變成一個非對象化的、緊耦合、到處是分散資料塊的糟糕系統。這樣的系統并不比傳統的兩層結構好到哪裡!也不會比jsp裡嵌入java代碼僞三層系統高明到什麼地方。

什麼是狀态?

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

  對于初學者,經常會疑問:我是将資料放在httpsession中還是request中,這裡 其實已經開始接觸狀态,一旦你接觸狀态,你就要開始小心,因為你可能會将記憶體洩漏的惡魔導引進來。

  記憶體洩漏的惡魔爆發時刻取決于你狀态的生存周期和系統并發通路量。

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

  狀态的危險還會發生在多線程環境下,當多個線程對同一個記憶體中狀态寫操作時,這時怎麼辦?如果這個狀态持久化在資料庫中,我們會依賴資料庫提供的強大事務機制防止這種并發死鎖,但是如果是在記憶體中,你就很難辦,是以,我們就盡量避免發生這種多線程同時通路一個狀态的現象,而singleton單例模式極容易發生這種現象,是以實踐中,單例模式是j2ee開發中需要避免的,相關文章讨論見:

<a href="http://www.jdon.com/jive/article.jsp?forum=91&amp;thread=17578" target="_blank">http://www.jdon.com/jive/article.jsp?forum=91&amp;thread=17578</a>

  我們接觸的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儲存狀态,這對叢集環境也是有利的,見該貼讨論:

<a href="http://www.jdon.com/jive/article.jsp?forum=121&amp;thread=22282" target="_blank">http://www.jdon.com/jive/article.jsp?forum=121&amp;thread=22282</a>

那麼這些狀态放在哪裡?使用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開發設計的三件寶呢?或者說三個典型知識點呢?我想通過本篇我已經通過狀态這個概念稍微解釋了域模組化的一些特點。

相關參考:

<a href="http://www.jdon.com/jive/article.jsp?forum=62&amp;thread=23720" target="_blank">學生課程評分系統的設計讨論</a>

<a href="http://www.jdon.com/artichect/dbover.htm" target="_blank">資料庫時代的終結</a>

<a href="http://www.jdon.com/jive/thread.jsp?forum=121&amp;thread=24624" target="_blank">用j2ee架構金融平台的問題</a>

<a href="http://www.jdon.com/mda/ddd.html" target="_blank">ddd(domain-driven design領域驅動設計)實戰</a>

<a href="http://www.jdon.com/jivejdon/key/ddd" target="_blank">更多關于evans ddd讨論</a>