這是一個(gè)實(shí)戰(zhàn)中非常重要但是容易被忽視的概念,說它重要,是因?yàn)樗?a href="http://m.survivalescaperooms.com/sql.asp">數(shù)據(jù)庫(kù)重要;說它容易被忽視也是同樣的原因,它經(jīng)常被數(shù)據(jù)庫(kù)概念替代。
如果你經(jīng)驗(yàn)和經(jīng)歷中沒有狀態(tài)這個(gè)概念,極端地說:可能你的java系統(tǒng)經(jīng)驗(yàn)還未積累到一定程度,狀態(tài)是每個(gè)Java程序員深入Java系統(tǒng)后必然碰到的問題。
本文我想試圖表達(dá)的是:狀態(tài)分兩種:活動(dòng)的狀態(tài)對(duì)象和持久化的狀態(tài)。而數(shù)據(jù)庫(kù)中的數(shù)據(jù)只是狀態(tài)的一種持久化結(jié)果,而Java系統(tǒng) 運(yùn)行時(shí),我們更多的可能是和一種活動(dòng)的狀態(tài)打交道,這種活動(dòng)的狀態(tài)存在內(nèi)存中,而不是持久化到硬盤上,當(dāng)然,需要時(shí)你可以通過數(shù)據(jù)庫(kù)/文件持久化到硬盤上。
但是,如果你以數(shù)據(jù)庫(kù)數(shù)據(jù)替代狀態(tài),那么就可能導(dǎo)致數(shù)據(jù)庫(kù)的頻繁訪問,而且 你的系統(tǒng)會(huì)變成一個(gè)非對(duì)象化的、緊耦合、到處是分散數(shù)據(jù)塊的糟糕系統(tǒng)。這樣的系統(tǒng)并不比傳統(tǒng)的兩層結(jié)構(gòu)好到哪里!也不會(huì)比jsp里嵌入Java代碼偽三層系統(tǒng)高明到什么地方。
什么是狀態(tài)?
只要有對(duì)象就可能有狀態(tài),任何一個(gè)對(duì)象活動(dòng)時(shí),都有自己的狀態(tài)屬性,類的 字段屬性極有可能成為狀態(tài),我們現(xiàn)在經(jīng)常使用的Domain model其實(shí)就是一種 包含狀態(tài)的對(duì)象,如果你對(duì)狀態(tài)沒有深入掌握,就不可能真正掌握對(duì)象系統(tǒng)特點(diǎn),或者是Domain Model的執(zhí)行情況。
對(duì)于初學(xué)者,經(jīng)常會(huì)疑問:我是將數(shù)據(jù)放在Httpsession中還是Request中,這里 其實(shí)已經(jīng)開始接觸狀態(tài),一旦你接觸狀態(tài),你就要開始小心,因?yàn)槟憧赡軙?huì)將內(nèi)存泄漏的惡魔導(dǎo)引進(jìn)來(lái)。
內(nèi)存泄漏的惡魔爆發(fā)時(shí)刻取決于你狀態(tài)的生存周期和系統(tǒng)并發(fā)訪問量。
狀態(tài)的生存周期也就是包含這個(gè)狀態(tài)的對(duì)象的生命周期,在簡(jiǎn)單系統(tǒng)中,我們只 需要通過new創(chuàng)建對(duì)象,然后它的消亡就會(huì)依靠JVM垃圾回收機(jī)制回收,但是事情會(huì)這么簡(jiǎn)單嗎?
狀態(tài)的危險(xiǎn)還會(huì)發(fā)生在多線程環(huán)境下,當(dāng)多個(gè)線程對(duì)同一個(gè)內(nèi)存中狀態(tài)寫操作時(shí),這時(shí)怎么辦?如果這個(gè)狀態(tài)持久化在數(shù)據(jù)庫(kù)中,我們會(huì)依賴數(shù)據(jù)庫(kù)提供的強(qiáng)大事務(wù)機(jī)制防止這種并發(fā)死鎖,但是如果是在內(nèi)存中,你就很難辦,因此,我們就盡量避免發(fā)生這種多線程同時(shí)訪問一個(gè)狀態(tài)的現(xiàn)象,而Singleton單例模式極容易發(fā)生這種現(xiàn)象,因此實(shí)踐中,單例模式是J2EE開發(fā)中需要避免的,相關(guān)帖子討論見:
http://www.jdon.com/jive/article.jsp?forum=91&thread=17578
我們接觸的Web容器或Jsp/Servlet本質(zhì)就是一個(gè)多線程,這也是很多初學(xué)者不知道的, 因?yàn)槎嗑€程編程是復(fù)雜或困難的,因此才有jsp/Servlet這樣的上層封裝,但是我們使用他們
時(shí),實(shí)際在進(jìn)行多線程編程。
生命周期和多線程并發(fā)使得我們簡(jiǎn)單的面向?qū)ο笙到y(tǒng)變得異常復(fù)雜和難以掌握起來(lái)。下面我從這個(gè)兩個(gè)角度,給出兩種模式思維解決之道。
生命周期(Scope)
生命周期(Scope)就是指狀態(tài)的活動(dòng)周期,狀態(tài)對(duì)象是什么時(shí)候被創(chuàng)建;然后什么時(shí)候被銷毀,很顯然,如果狀態(tài)對(duì)象還沒有被創(chuàng)建或已經(jīng)被銷毀,你再訪問這個(gè)狀態(tài)對(duì)象可能失敗,而狀態(tài)的生命周期控制是可能散落在運(yùn)行程序的各個(gè)地方,如果不象狀態(tài)模式那樣進(jìn)行統(tǒng)一控制,有可能整個(gè)系統(tǒng)是危機(jī)四伏的。
狀態(tài)的生命周期其實(shí)就是對(duì)象生命周期,更加細(xì)化地說:是Domain Model這個(gè)對(duì)象的生命周期。這在一個(gè)以領(lǐng)域模型為驅(qū)動(dòng)的設(shè)計(jì)概念中不可回避的課題,而領(lǐng)域模型實(shí)戰(zhàn)的復(fù)雜性就復(fù)雜在此。
狀態(tài)的生命周期在J2EE中目前有三種:Request/Session和application,Request是每個(gè)客戶端發(fā)出的一次請(qǐng)求,這是 J2EE系統(tǒng)中最基本的事件激活單元,當(dāng)服務(wù)器端推出一個(gè)頁(yè)面到客戶端時(shí),意味著這個(gè)Request的結(jié)束。那么如果我們的狀態(tài)保存在Request中,意味著在request結(jié)束之前,這個(gè)請(qǐng)求經(jīng)歷的任何一個(gè)環(huán)節(jié)都可以對(duì)這個(gè)狀態(tài)(對(duì)象)進(jìn)行操作。(掌握這個(gè)原理,對(duì)于你學(xué)習(xí)Struts和JSF很有幫助)
如果是Session,則一直和該客戶端有關(guān),只要是該客戶端發(fā)出的每次request的任何環(huán)節(jié)都可以對(duì)這個(gè)狀態(tài)(對(duì)象)進(jìn)行操作。
如果是Application,則意味著這個(gè)狀態(tài)是當(dāng)前Web項(xiàng)目的全局狀態(tài)。
這三種狀態(tài)形式都是以將狀態(tài)保存在內(nèi)存中形式存在的,是和持久化狀態(tài)相對(duì)的。是一種內(nèi)存活動(dòng)狀態(tài)。
生命周期的選取當(dāng)然是越短越好,這樣,這個(gè)狀態(tài)對(duì)象就可以被自動(dòng)銷毀,從而避免了
大訪問量下的內(nèi)存泄漏,但是在大訪問量下,對(duì)象頻繁創(chuàng)建和銷毀是耗費(fèi)性能的。
那么,我們可能經(jīng)常使用HttpSession來(lái)保存狀態(tài),這時(shí)你極有可能造成內(nèi)存泄漏,我經(jīng)常在Jdon論壇上看到將很多數(shù)據(jù)庫(kù)數(shù)據(jù)暫時(shí)保存在 HttpSession中想法,這是相當(dāng)危險(xiǎn)的,因?yàn)橐坏┎l(fā)用戶很多,相當(dāng)多的HttpSession包含了狀態(tài),而狀態(tài)中有可能有更多其他引用,因此內(nèi)存很快會(huì)爆滿,或者垃圾回收機(jī)制頻繁啟動(dòng),造成應(yīng)用系統(tǒng)運(yùn)行暫停或緩慢。
當(dāng)你將狀態(tài)放入HttpSession時(shí),難道沒有考慮將其手工消除嗎?你要知道所有Web容器(Tomcat/Weblogic等)都不會(huì)自動(dòng)替你清除那些你可能不用的狀態(tài)對(duì)象啊。如果每個(gè)人只管新增元素,不管重整或管理,這個(gè)系統(tǒng)能不變得混亂嗎?代碼上這種現(xiàn)象我們是通過Refactoring等結(jié)構(gòu)/行為模式來(lái)解決,那么在運(yùn)行時(shí)的狀態(tài)管理呢?
狀態(tài)管理模式或者說對(duì)象管理模式正是解決這種問題的。
按照該模式,你必須手工自己管理放在HttpSession的狀態(tài),比如你為每個(gè)HttpSession
設(shè)立一個(gè)狀態(tài)容器最大尺寸,當(dāng)超過這個(gè)尺寸時(shí),你需要將不用的狀態(tài)從容器去除, 但是如果這個(gè)客戶端在Session失效期內(nèi)又來(lái)訪問這個(gè)狀態(tài)怎么辦?那么你可能需要先臨時(shí)將狀態(tài)序列化保存到硬盤上,等Session失效期到達(dá)后再真正刪除。
是不是覺得很麻煩?
捷徑是有:
1. 盡量少使用HttpSession保存狀態(tài),這對(duì)集群環(huán)境也是有利的,見該貼討論:
http://www.jdon.com/jive/article.jsp?forum=121&thread=22282
那么這些狀態(tài)放在哪里?使用Application的緩存中,
2. 使用狀態(tài)管理中間件,目前有幾個(gè)選擇:EJB的有態(tài)Bean;NanoContainer之類狀態(tài)相關(guān)的微容器。那么SPRing可以嗎?目前沒有發(fā)現(xiàn)有該功能,甚至在Spring容器內(nèi)無(wú)法直接使用Session性質(zhì)的狀態(tài),只能通過線程級(jí)別的ThreadLocal來(lái)實(shí)現(xiàn)(對(duì)不起,你又要開始回到遠(yuǎn)古的匯編線程時(shí)代了);而Jdon框架則可以。
下面我們談?wù)凙pplication的狀態(tài),在這個(gè)范圍內(nèi),一個(gè)對(duì)象狀態(tài)可以被多個(gè)用戶反復(fù)訪問,在這個(gè)級(jí)別,狀態(tài)類似數(shù)據(jù)庫(kù)中數(shù)據(jù),因?yàn)榭梢允褂脭?shù)據(jù)庫(kù)來(lái)替代這個(gè)級(jí)別的狀態(tài),所以將狀態(tài)放入緩存這個(gè)深層次技術(shù)被大多數(shù)初學(xué)者忽視了,甚至產(chǎn)生了對(duì)數(shù)據(jù)庫(kù)依賴心理。
緩存中的狀態(tài)
雖然我們將狀態(tài)保存在Application中,但是我們不可避免還是遇到Session同樣的狀態(tài)管理問題,這個(gè)問題所幸的是有專門緩存中間件解決了,當(dāng)然,在一個(gè)多服務(wù)器集群系統(tǒng),如果一個(gè)客戶端在一個(gè)服務(wù)器中存放了狀態(tài),那么能否在另外一個(gè)服務(wù)器的內(nèi)存中訪問到呢?回答是肯定的,前提是你必須使用分布式緩存系統(tǒng)。
目前分布式緩存系統(tǒng)是靠EJB服務(wù)器完成,當(dāng)JBoss 5在2006變成完全解耦、可肢解時(shí),
我們就可以使用原本只支持EJB的JBoss分布式緩存系統(tǒng)來(lái)支持我們的普通JavaBeans了(POJO)。這其中目前可能會(huì)花費(fèi)一些力氣,因?yàn)檫€沒有一個(gè)統(tǒng)一的POJO構(gòu)件接口標(biāo)準(zhǔn),我相信以后
可能會(huì)有。
如果你不想花費(fèi)力氣,而且可能就只是一臺(tái)服務(wù)器,可以通過雙核芯片提升性能,那么單態(tài)緩存如果實(shí)現(xiàn)?很簡(jiǎn)單,使用一個(gè)緩存產(chǎn)品如OsCache等,將其設(shè)定保存在 Application中,或者在web.xml中進(jìn)行一下簡(jiǎn)單的配置即可。
但是,這時(shí)你可能碰到另外一個(gè)問題:狀態(tài)的唯一標(biāo)識(shí),如何通過唯一標(biāo)識(shí)從緩存中那么
多對(duì)象狀態(tài)中取出你要的那一個(gè)呢?比較瑣碎。
有沒有一個(gè)框架幫助你省卻這些麻煩,當(dāng)然推薦Jdon Framework,只要將包含狀態(tài)的類(主要是Domain Model)繼承特定的類或接口(接口在1.4版本實(shí)現(xiàn))即可,這個(gè)類的對(duì)象運(yùn)行時(shí)就會(huì)被緩存或從緩存中讀取,再也無(wú)需你照料緩存了,就這么簡(jiǎn)單。
當(dāng)然,Jdon Framework的底層緩存器是可以被替代,使用你喜歡的緩存產(chǎn)品,因?yàn)閖don
Framework是基于Ioc設(shè)計(jì),構(gòu)件之間是完全解耦、可徹底肢解,能夠通過配置替代和更換的。
如果你不明白這個(gè)道理,需要好好研究一下Ioc模式帶給我們革命性的新變化。
從以上也可以看出:java復(fù)雜性還在于我們需要在編碼時(shí),卻要想象其運(yùn)行時(shí)的情形。而這種翻譯聯(lián)想沒有深厚的實(shí)踐功底,是很難順利完成的。
狀態(tài)管理中間件
自從J2EE開辟中間件時(shí)代以來(lái),就有相當(dāng)多的高級(jí)中間件提供與具體應(yīng)用無(wú)關(guān)的通用功能,狀態(tài)管理中間件很早就有之,EJB的有態(tài)Session Bean是一個(gè)代表。
一個(gè)中間件不但要有良好的松耦合設(shè)計(jì),我們暫時(shí)稱為靜態(tài)設(shè)計(jì);更要有優(yōu)秀的動(dòng)態(tài)設(shè)計(jì),例如狀態(tài)管理就屬于一種動(dòng)態(tài)設(shè)計(jì)。
當(dāng)然,如果你比較謙虛,不但要選擇一些靜態(tài)設(shè)計(jì)很好的框架或中間件;而且還要依賴一些擁有良好的動(dòng)態(tài)運(yùn)行管理的中間件。
EJB無(wú)論是EJB1.X/EJB2.X/EJB3.X.在狀態(tài)管理上要更加優(yōu)秀,當(dāng)然EJB3.X又吸收了優(yōu)秀的靜態(tài)設(shè)計(jì)概念,但是因?yàn)樾枰幸粋€(gè)具體服務(wù)器實(shí)現(xiàn)過程,這個(gè)過程中存在一些陷阱,如In-Box問題等。
Spring無(wú)疑是一個(gè)靜態(tài)設(shè)計(jì)非常優(yōu)秀框架,它一直在AOP上孜孜不倦,力圖探索一條從AOP角度進(jìn)行動(dòng)態(tài)運(yùn)行管理干預(yù)捷徑,相信會(huì)有驚人結(jié)果,當(dāng)然,這種細(xì)粒度的AOP需要實(shí)踐檢驗(yàn),當(dāng)然如果整入JDK 6.0就更好。
而Jdon Framework則試圖在目前兩者之間尋求了一個(gè)平衡,既有Ioc/AOP優(yōu)秀的靜態(tài)設(shè)計(jì),雖然在AOP上不及Spring前衛(wèi);但提供了切實(shí)Session和Cache狀態(tài)管理;
如果你不需要EJB的分布式多服務(wù)器集群功能;又不是AOP的超級(jí)粉絲,無(wú)疑使用Jdon Framework之類的框架無(wú)疑是簡(jiǎn)化方便的。
狀態(tài)設(shè)計(jì)的難點(diǎn)
最后,我不得不重申,并不是有了良好的狀態(tài)管理框架就可以高枕無(wú)憂了,狀態(tài)的設(shè)計(jì)其實(shí)是我們每個(gè)項(xiàng)目必須面臨的可變課題,如果狀態(tài)復(fù)雜了可以使用狀態(tài)模式對(duì)付,可惜往往狀態(tài)不夠復(fù)雜。
一個(gè)對(duì)象本身屬性和狀態(tài)是應(yīng)該耦合在一起,還是進(jìn)行分離,屬性和狀態(tài)沒有明顯的涇渭分明的界限,我們舉一個(gè)例子:
論壇Forum這個(gè)對(duì)象,它有一些字段屬性,如論壇名稱、論壇描述,還有其他一些相關(guān)屬性:如該論壇的最新發(fā)帖;該論壇的發(fā)貼量,后兩者好像也是論壇字段,但是他們可能經(jīng)常變化的,應(yīng)該屬于狀態(tài),那么狀態(tài)和Forum這個(gè)主體對(duì)象是什么關(guān)系?是將該論壇的最新發(fā)帖和該論壇的發(fā)貼量?jī)蓚€(gè)字段并入Forum 這個(gè)Domain Model中,還是應(yīng)該單獨(dú)建立一個(gè)狀態(tài)對(duì)象?如果進(jìn)行分離,分離的依據(jù)是什么?
當(dāng)然,這里分離的依據(jù)是因?yàn)閷?duì)象的生存周期不同。對(duì)于我們熟悉的課題,我們能夠馬上分辨出其中的生存周期,如果是不熟悉領(lǐng)域的建模呢?
所以,大家已經(jīng)明白:狀態(tài)設(shè)計(jì)的難點(diǎn)是:如何粒度細(xì)化地創(chuàng)建模型對(duì)象;然后分辨出其中動(dòng)態(tài)的狀態(tài)性質(zhì)。這是域建模實(shí)戰(zhàn)中一個(gè)難點(diǎn)。
很多人問我:你提倡的域建模、設(shè)計(jì)模式和框架是什么意思?為什么說他們是Java開發(fā)設(shè)計(jì)的三件寶呢?或者說三個(gè)典型知識(shí)點(diǎn)呢?我想通過本篇我已經(jīng)通過狀態(tài)這個(gè)概念稍微解釋了域建模的一些特點(diǎn)。
當(dāng)前,MDA中的四色原型模式Archetype將幫助我們更好地分辨出類的屬性、狀態(tài)和行為,這是一場(chǎng)帶來(lái)以后十年的軟件革命。
|
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注