MVC 框架可以將業(yè)務(wù)邏輯(Java beans 和 EJB 組件)、控制器邏輯(Servlets/Struts 動(dòng)作)、表示層(JSP、XML/XSLT)清晰地分離開來。良好的分層可以帶來許多好處。
MVC 框架對(duì)于成功使用 J2EE 是如此重要,以致沒有其他最佳實(shí)踐可以與其相提并論。模型-視圖-控制器(MVC)是設(shè)計(jì) J2EE 應(yīng)用程序的基礎(chǔ)。MVC 將您的程序代碼簡(jiǎn)單地劃分下面幾個(gè)部分:
·負(fù)責(zé)業(yè)務(wù)邏輯的代碼(即模型——通常使用 EJB 或者普通的 Java 對(duì)象來實(shí)現(xiàn))。
·負(fù)責(zé)用戶界面顯示的代碼(即視圖——通常通過 JSP 及標(biāo)記庫(kù)來實(shí)現(xiàn),有時(shí)也使用 XML 和 XSLT 來實(shí)現(xiàn))。
·負(fù)責(zé)應(yīng)用程序流程的代碼(即控制器——通常使用 Java Servlet 或像 Struts 控制器這樣的類來實(shí)現(xiàn))。
如果您不遵循基本的 MVC 框架,在開發(fā)過程中就會(huì)出現(xiàn)許多的問題。最常見的問題就是在視圖部分添加了太多的成分,例如,可能存在使用 JSP 標(biāo)記來執(zhí)行數(shù)據(jù)庫(kù)訪問,或者在 JSP 中進(jìn)行應(yīng)用程序的流程控制,這在小規(guī)模的應(yīng)用程序中是比較常見的,但是,隨著后期的開發(fā),這樣做將會(huì)帶來問題,因?yàn)?JSP 逐步變得越來越難以維護(hù)和調(diào)試。
類似地,我們也經(jīng)?吹綄⒁晥D層構(gòu)建到業(yè)務(wù)邏輯的情況。例如,一個(gè)常見的問題就是將在構(gòu)建視圖時(shí)使用的 XML 解析技術(shù)直接應(yīng)用到業(yè)務(wù)層。業(yè)務(wù)層應(yīng)該對(duì)業(yè)務(wù)對(duì)象——而不是綁定到視圖的特定數(shù)據(jù)表示進(jìn)行操作。
然而,只是具有合適的組件并不一定意味著可以使您的應(yīng)用程序得到合適的分層。我們常常見到一些應(yīng)用程序包含 servlet、JSP 和 EJB 組件所有這三項(xiàng),然而,其主要的業(yè)務(wù)邏輯卻是在 servlet 層實(shí)現(xiàn)的,或者應(yīng)用程序?qū)Ш绞窃?JSP 中處理的。您必須進(jìn)行嚴(yán)格的代碼檢查并重構(gòu)您的代碼,以確保應(yīng)用程序的業(yè)務(wù)邏輯只在模型層(Model layer)進(jìn)行處理,應(yīng)用程序?qū)Ш街煌ㄟ^控制器層(Controller layer)進(jìn)行處理,而您的視圖(Views)只是將傳遞過來的模型對(duì)象以 HTML 及 JavaScript 的形式表示出來。
2. 在應(yīng)用程序的每一層都使用自動(dòng)單元測(cè)試和測(cè)試管理。
不要只是測(cè)試您的圖形用戶界面(GUI)。分層的測(cè)試使測(cè)試及維護(hù)工作變得極其簡(jiǎn)單。
在過去的幾年中,在方法學(xué)領(lǐng)域有了相當(dāng)大的革新,例如新出現(xiàn)的被稱為 Agile(例如 SCRUM [Schwaber] 和極限編程 [Beck1])的輕量級(jí)方法現(xiàn)在已經(jīng)得到了很普遍的應(yīng)用。幾乎所有的這些方法中的一個(gè)共同的特征是它們都提倡使用自動(dòng)的測(cè)試工具,這些工具可以幫助開發(fā)人員用更少的時(shí)間進(jìn)行回歸測(cè)試 (regression testing),并可以幫助他們避免由于不充分的回歸測(cè)試造成的錯(cuò)誤,因此可以用來提高程序員的工作效率。實(shí)際上,還有一種被稱為 Test-First Development [Beck2] 的方法,這種方法甚至提倡在開發(fā)實(shí)際的代碼之前就先編寫單元測(cè)試。然而,在您測(cè)試代碼之前,您需要將代碼分割成一些可測(cè)試的片斷。一個(gè)"大泥球"是難以測(cè)試的,因?yàn)樗皇侵粚?shí)現(xiàn)一個(gè)簡(jiǎn)單的易于識(shí)別的功能。如果您的每個(gè)代碼片斷實(shí)現(xiàn)多個(gè)方面的功能,這樣的代碼將難以保證其完全的正確性。
MVC 框架(以及 J2EE 中的 MVC 實(shí)現(xiàn))的一個(gè)優(yōu)點(diǎn)就是元素的組件化能夠(實(shí)際上,相當(dāng)?shù)暮?jiǎn)單)對(duì)您的應(yīng)用程序進(jìn)行單元測(cè)試。因此,您可以方便地對(duì)實(shí)體 bean、會(huì)話 bean 以及 JSP 獨(dú)立編寫測(cè)試用例,而不必考慮其他的代碼。現(xiàn)在有許多用于 J2EE 測(cè)試的框架和工具,這些框架及工具使得這一過程更加簡(jiǎn)單。例如,JUnit(是一種由 junit.org 開發(fā)的開放源代碼工具)和 Cactus(由 Apache 開發(fā)的開放源代碼工具)對(duì)于測(cè)試 J2EE 組件都非常有用。[Hightower] 詳細(xì)探討了如何在 J2EE 中使用這些工具。
盡管所有這些詳述了怎樣徹底地測(cè)試您的應(yīng)用程序,但是我們?nèi)匀豢吹揭恍┤苏J(rèn)為只要他們測(cè)試了 GUI(可能是基于 Web 的 GUI,或者是獨(dú)立的 Java 應(yīng)用程序),則他們就全面地測(cè)試了整個(gè)應(yīng)用程序。GUI 測(cè)試很難達(dá)到全面的測(cè)試,有以下幾種原因。首先,使用 GUI 測(cè)試很難徹底地測(cè)試到系統(tǒng)的每一條路徑,GUI 僅僅是影響系統(tǒng)的一種方式,可能存在后臺(tái)運(yùn)算、腳本和各種各樣的其他訪問點(diǎn),這也需要進(jìn)行測(cè)試。然而,它們通常并不具有 GUI。第二,GUI 級(jí)的測(cè)試是一種非常粗粒度的測(cè)試。這種測(cè)試只是在宏觀水平上測(cè)試系統(tǒng)的行為。這意味著一旦發(fā)現(xiàn)存在問題,則與此問題相關(guān)的整個(gè)子系統(tǒng)都要進(jìn)行檢查,這使得找出 bug(缺陷)將是非常困難的事情。第三,GUI 測(cè)試通常只有在整個(gè)開發(fā)周期的后期才能很好地得到測(cè)試,這是因?yàn)橹挥羞@那個(gè)時(shí)候 GUI 才得到完整的定義。這意味著只有在后期才可能發(fā)現(xiàn)潛在的 bug。第四,一般的開發(fā)人員可能沒有自動(dòng)的 GUI 測(cè)試工具。因此,當(dāng)一個(gè)開發(fā)人員對(duì)代碼進(jìn)行更改時(shí),沒有一種簡(jiǎn)單的方法來重新測(cè)試受到影響的子系統(tǒng)。這實(shí)際上不利于進(jìn)行良好的測(cè)試。如果開發(fā)人員具有自動(dòng)的代碼級(jí)單元測(cè)試工具,開發(fā)人員就能夠很容易地運(yùn)行這些工具以確保所做的更改不會(huì)破壞已經(jīng)存在的功能。最后,如果添加了自動(dòng)構(gòu)建功能,則在自動(dòng)構(gòu)建過程中添加一個(gè)自動(dòng)的單元測(cè)試工具是非常容易的事情。當(dāng)完成這些設(shè)置以后,整個(gè)系統(tǒng)就可以有規(guī)律地進(jìn)行重建,并且回歸測(cè)試幾乎不需要人的參與。
另外,我們必須強(qiáng)調(diào),使用 EJB 和 Web 服務(wù)進(jìn)行分布式的、基于組件的開發(fā)使得測(cè)試單個(gè)的組件變得非常必要。如果沒有"GUI"需要測(cè)試,您就必須進(jìn)行低級(jí)(lower-level)測(cè)試。最好以這種方式開始測(cè)試,省得當(dāng)您將分布式的組件或 Web 服務(wù)作為您的應(yīng)用程序的一部分時(shí),您不得不花費(fèi)心思重新進(jìn)行測(cè)試。
總之,通過使用自動(dòng)的單元測(cè)試,能夠很快地發(fā)現(xiàn)系統(tǒng)的缺陷,并且也易于發(fā)現(xiàn)這些缺陷,使得測(cè)試工作變得更加系統(tǒng)化,因此整體的質(zhì)量也得以提高。
3. 按照規(guī)范來進(jìn)行開發(fā),而不是按照應(yīng)用服務(wù)器來進(jìn)行開發(fā)。
要將規(guī)范熟記于心,如果要背離規(guī)范,要經(jīng)過慎密的考慮后才可以這樣做。這是因?yàn)楫?dāng)您背離規(guī)則的時(shí)候,您所做的事情往往并不是您應(yīng)該做的事情。
當(dāng)您要背離 J2EE 可以允許您做的事情的時(shí)候,這很容易讓使您遭受不幸。我們發(fā)現(xiàn)有一些開發(fā)人員鉆研一些 J2EE 允許之外的東西,他們認(rèn)為這樣做可以"稍微"改善J2EE的性能,而他們最終只會(huì)發(fā)現(xiàn)這樣做會(huì)引起嚴(yán)重的性能問題,或者在以后的移植(從一個(gè)廠商到另一個(gè)廠商,或者是更常見的從一個(gè)版本到另一個(gè)版本)中會(huì)出現(xiàn)問題。實(shí)際上,這種移植問題是如此嚴(yán)重,以致 [Beaton] 將此原則稱為移植工作的基本最佳實(shí)踐。
現(xiàn)在有好幾個(gè)地方如果不直接使用 J2EE 提供的方法肯定會(huì)產(chǎn)生問題。一個(gè)常見的例子就是開發(fā)人員通過使用 JAAS 模塊來替代 J2EE 安全性,而不是使用內(nèi)置的遵循規(guī)范的應(yīng)用程序服務(wù)器機(jī)制來進(jìn)行驗(yàn)證和授權(quán)。要注意不要脫離 J2EE 規(guī)范提供的驗(yàn)證機(jī)制,如果脫離了此規(guī)范,這將是系統(tǒng)存在安全漏洞以及廠商兼容性問題的主要原因。類似地,要使用 servlet 和 EJB 規(guī)范提供的授權(quán)機(jī)制,并且如果您要偏離這些規(guī)范的話,要確保使用規(guī)范定義的 API(例如 getCallerPrincipal())作為實(shí)現(xiàn)的基礎(chǔ)。通過這種方式,您將能夠利用廠商提供的強(qiáng)安全性基礎(chǔ)設(shè)施,其中,業(yè)務(wù)要求需要支持復(fù)雜的授權(quán)規(guī)則。
其他常見的問題包括使用不遵循 J2EE 規(guī)范的持久性機(jī)制(這使得事務(wù)管理變得困難)、在J2EE程序中使用不適當(dāng)?shù)腏2SE 方法(例如線程或 singleton),以及使用您自己的方法解決程序到程序(program-to-program)的通信,而不是使用 J2EE 內(nèi)在支持的機(jī)制(例如 JCA、JMS 或 Web 服務(wù))。當(dāng)您將一個(gè)遵循 J2EE 的服務(wù)器移植到其他的服務(wù)器上,或者移植到相同服務(wù)器的新版本上,上述的設(shè)計(jì)選擇將會(huì)造成無數(shù)的問題。唯一要背離規(guī)范的情況是,當(dāng)一個(gè)問題在規(guī)范的范圍內(nèi)無法解決的時(shí)候。例如,安排執(zhí)行定時(shí)的業(yè)務(wù)邏輯在 EJB2.1 出現(xiàn)之前是一個(gè)問題,在類似這樣的情況下,我們建議當(dāng)有廠商提供的解決方案時(shí)就使用廠商提供的解決方案(例如 WebSphere Application Server Enterprise 中的 Scheduler 工具),而在沒有廠商提供的解決方案時(shí)就使用第三方提供的工具。如果使用廠商提供的解決方案,應(yīng)用程序的維護(hù)以及將其移植到新的規(guī)范版本將是廠商的問題,而不是您的問題。
最后,要注意不要太早地采用新技術(shù)。太過于熱衷采用還沒有集成到 J2EE 規(guī)范的其他部分或者還沒有集成到廠商的產(chǎn)品中的技術(shù)常會(huì)帶來災(zāi)難性的后果。支持是關(guān)鍵的——如果您的廠商不直接支持一種特定的在 JSR 中提出的技術(shù),但此技術(shù)還沒有被 J2EE 所接受,那么您就不應(yīng)該采用此技術(shù)。畢竟,我們中的大多數(shù)人從事解決業(yè)務(wù)問題,而不是推進(jìn)技術(shù)的發(fā)展。
4. 從一開始就計(jì)劃使用 J2EE 安全性。
啟用 WebSphere 安全性。這使您的 EJB 和 URL 至少可以讓所有授權(quán)用戶訪問。不要問為什么——照著做就是了。
在與我們合作的客戶中,一開始就打算啟用 WebSphere J2EE 安全性的顧客是非常少的,這一點(diǎn)一直讓我們感到吃驚。據(jù)我們估計(jì)大約只有 50% 的顧客一開始就打算使用此特性。例如,我們?cè)c一些大型的金融機(jī)構(gòu)(銀行、代理等等)合作過,他們也沒有打算啟用安全性。幸運(yùn)的是,這種問題在部署之前的檢查時(shí)就得以解決.
不使用 J2EE 安全性是危險(xiǎn)的事情。假設(shè)您的應(yīng)用程序需要安全性(幾乎所有的應(yīng)用程序都需要),您敢打賭您的開發(fā)人員能夠構(gòu)建出自己的安全性系統(tǒng),而這個(gè)系統(tǒng)比您從 J2EE 廠商那里買來的更好。這可不是個(gè)好的賭注,為分布式的應(yīng)用程序提供安全性是異常困難的。例如,您需要使用網(wǎng)絡(luò)安全加密令牌控制對(duì) EJB 的訪問。以我們的經(jīng)驗(yàn)看來,大多數(shù)自己構(gòu)建的安全性系統(tǒng)是不安全的,并且有重大的缺陷,這使產(chǎn)品系統(tǒng)極其脆弱。
一些不使用 J2EE 安全性的理由包括:擔(dān)心性能的下降,相信其他的安全性(例如 Netegrity SiteMinder)可以取代 J2EE 安全性,或者是不知道 WebSphere Application Server 安全特性及功能。不要陷入這些陷阱之中,尤其是,盡管像 Netegrity SiteMinder 這樣的產(chǎn)品能夠提供優(yōu)秀的安全特性,但是僅僅其自身不可能保護(hù)整個(gè) J2EE 應(yīng)用程序。這些產(chǎn)品必須與 J2EE 應(yīng)用程序服務(wù)器聯(lián)合起來才可能全面地保護(hù)您的系統(tǒng)。
其他的一種常見的不使用 J2EE 安全性的原因是:基于角色的模型沒有提供足夠的粒度訪問控制以滿足復(fù)雜的業(yè)務(wù)規(guī)則。盡管事實(shí)是這樣的,但這也不應(yīng)該成為不使用 J2EE 安全性的理由。相反地,應(yīng)該將 J2EE 驗(yàn)證及 J2EE 角色與特定的擴(kuò)展規(guī)則結(jié)合起來。如果復(fù)雜的業(yè)務(wù)規(guī)則需要做出安全性決策,那就編寫相應(yīng)的代碼,其安全性決策要基于可以直接使用的以及可靠的 J2EE 驗(yàn)證信息(用戶 ID 和角色)。
5. 創(chuàng)建您所知道的。
反復(fù)的開發(fā)工作將使您能夠逐漸地掌握所有的 J2EE 模塊。要從創(chuàng)建小而簡(jiǎn)單的模塊開始而不是從一開始就馬上涉及到所有的模塊。
我們必須承認(rèn) J2EE 是龐大的體系。如果一個(gè)開發(fā)小組只是開始使用 J2EE,這將很難一下子就能掌握它。在 J2EE 中有太多的概念和 API 需要掌握。在這種情況下,成功掌握 J2EE 的關(guān)鍵是從簡(jiǎn)單的步驟開始做起。
這種方法可以通過在您的應(yīng)用程序中創(chuàng)建小而簡(jiǎn)單的模塊來得到最好的實(shí)現(xiàn)。如果一個(gè)開發(fā)小組通過創(chuàng)建一個(gè)簡(jiǎn)單的域模型以及后端的持久性機(jī)制(也許使用的是 JDBC),并且對(duì)其進(jìn)行了完整的測(cè)試,這會(huì)增強(qiáng)他們的自信心,于是他們會(huì)使用該域模型去掌握使用 servlet 和 JSP 的前端開發(fā)。如果一個(gè)開發(fā)組發(fā)現(xiàn)有必要使用 EJB,他們也會(huì)類似地開始在容器管理的持久性 EJB 組件之上使用簡(jiǎn)單的會(huì)話 Facades,或者使用基于 JDBC 的數(shù)據(jù)訪問對(duì)象(JDBC-based Data Access Objects,DAO),而不是跳過這些去使用更加復(fù)雜的構(gòu)造(例如消息驅(qū)動(dòng)bean和JMS)。
這種方法并不是什么新方法,但是很少有開發(fā)組以這種方式來培養(yǎng)他們的技能。相反地,多數(shù)開發(fā)組由于嘗試馬上就構(gòu)建所有的模塊,同時(shí)涉及 MVC 中的視圖層、模型層和控制器層,這樣做的結(jié)果是他們往往會(huì)陷入進(jìn)度的壓力之中。他們應(yīng)該考慮一些敏捷(Agile)開發(fā)方法,例如極限編程(XP),這種開發(fā)方法采用一種增量學(xué)習(xí)及開發(fā)方法。在 XP 中有一種稱為 ModelFirst 的過程,這個(gè)過程涉及到首先構(gòu)建域模型作為一種機(jī)制來組織和實(shí)現(xiàn)用戶場(chǎng)景;菊f來,您要構(gòu)建域模型作為您要實(shí)現(xiàn)的用戶場(chǎng)景的首要部分,然后在域模型之上構(gòu)建一個(gè)用戶界面(UI)作為用戶場(chǎng)景實(shí)現(xiàn)的結(jié)果。這種方法非常適合讓一個(gè)開發(fā)組一次只學(xué)到一種技術(shù),而不是讓他們同時(shí)面對(duì)很多種情況(或者讓他們讀很多書),這會(huì)令他們崩潰的。
還有,對(duì)每個(gè)應(yīng)用程序?qū)又貜?fù)的開發(fā)可能會(huì)包含一些適當(dāng)?shù)哪J郊白罴褜?shí)踐。如果您從應(yīng)用程序的底層開始應(yīng)用一些模式如數(shù)據(jù)訪問對(duì)象和會(huì)話 Facades,您就不應(yīng)該在您的JSP和其他視圖對(duì)象中使用域邏輯。
最后,當(dāng)您開發(fā)一些簡(jiǎn)單的模塊時(shí),在開始的初期就可以對(duì)您的應(yīng)用程序進(jìn)行性能測(cè)試。如果直到應(yīng)用程序開發(fā)的后期才進(jìn)行性能測(cè)試的話,這往往會(huì)出現(xiàn)災(zāi)難性的后果。
6. 當(dāng)使用 EJB 組件時(shí),始終使用會(huì)話 Facades。
決不要將實(shí)體 bean 直接暴露給任何用戶類型。對(duì)實(shí)體 bean 只可以使用本地 EJB 接口(Local EJB interfaces)。
當(dāng)使用 EJB 組件時(shí),使用一個(gè)會(huì)話 Facades 是一個(gè)確認(rèn)無疑的最佳實(shí)踐。實(shí)際上,這個(gè)通用的實(shí)踐被廣泛地應(yīng)用到任何分布式的技術(shù)中,包括 CORBA、EJB 和 DCOM。從根本上講,您的應(yīng)用程序的分布"交叉區(qū)域"越是底層化,對(duì)小塊的數(shù)據(jù)由于多次重復(fù)的網(wǎng)絡(luò)中繼造成的時(shí)間消耗就越少。要達(dá)到這個(gè)目的的方法就是:創(chuàng)建大粒度的 Facades 對(duì)象,這個(gè)對(duì)象包含邏輯子系統(tǒng),因而可以通過一個(gè)方法調(diào)用就可以完成一些有用的業(yè)務(wù)功能。這種方法不但能夠降低網(wǎng)絡(luò)開銷,而且在 EJB 內(nèi)部通過為整個(gè)業(yè)務(wù)功能創(chuàng)建一個(gè)事務(wù)環(huán)境也可以大大地減少對(duì)數(shù)據(jù)庫(kù)的訪問次數(shù)。
EJB 本地接口(從 EJB 2.0 規(guī)范開始使用)為共存的 EJB 提供了性能優(yōu)化方法。本地接口必須被您的應(yīng)用程序顯式地進(jìn)行訪問,這需要代碼的改變和防止以后配置 EJB 時(shí)需要應(yīng)用程序的改變。由于會(huì)話 Facades 和它包含的整個(gè) EJB 對(duì)于彼此來說都應(yīng)該是本地的,我們建議對(duì)會(huì)話 Facades 后面的實(shí)體 bean 使用本地接口。然而,會(huì)話 Facades 本身的實(shí)現(xiàn)(典型例子如無狀態(tài)會(huì)話 bean)應(yīng)該設(shè)計(jì)為遠(yuǎn)程接口。
為了性能的優(yōu)化,可以將一個(gè)本地接口添加到會(huì)話 Facades。這樣做利用了這樣一個(gè)事實(shí):在大多數(shù)情況下(至少在 Web 應(yīng)用程序中),您的 EJB 客戶端和 EJB 會(huì)共同存在于同一個(gè) Java 虛擬機(jī)(JVM)中。另外一種情況,如果會(huì)話 Facades 在本地被調(diào)用,可以使用 J2EE 應(yīng)用服務(wù)器配置優(yōu)化(configuration optimizations),例如 WebSphere 中的"No Local Copies"。然而,您必須注意到這些可供選擇的方案會(huì)將交互方法從按值傳遞(pass-by-value)改變?yōu)榘匆脗鬟f(pass-by-reference)。這可能會(huì)在您的代碼中產(chǎn)生很微妙的錯(cuò)誤。當(dāng)您要利用這些方案時(shí),您應(yīng)該在一開始就考慮其可行性。
如果在您的會(huì)話 Facades 中使用遠(yuǎn)程接口(而不是本地接口),您也可以將同樣的會(huì)話 Facades 在 J2EE 1.4 中以兼容的方式作為 Web 服務(wù)來配置。這是因?yàn)?JSR 109(J2EE 1.4 中的 Web 服務(wù)部署部分)要求使用無狀態(tài)會(huì)話 bean 的遠(yuǎn)程接口作為 EJB Web 服務(wù)和 EJB 實(shí)現(xiàn)的接口。這樣做是值得的,因?yàn)檫@樣做可以為您的業(yè)務(wù)邏輯增加客戶端類型的數(shù)量。
7. 使用無狀態(tài)會(huì)話 bean,而不是有狀態(tài)會(huì)話 bean。
這樣做可以使您的系統(tǒng)經(jīng)得起錯(cuò)誤的終止。使用 HttpSession 存儲(chǔ)和用戶相關(guān)的狀態(tài)。
以我們的觀點(diǎn)看來,有狀態(tài)會(huì)話 bean 的概念已經(jīng)過時(shí)了。如果您仔細(xì)對(duì)其考慮一下,一個(gè)有狀態(tài)會(huì)話 bean 實(shí)際上與一個(gè) CORBA 對(duì)象在體系結(jié)構(gòu)上是完全相同的,無非就是一個(gè)對(duì)象實(shí)例,綁定到一個(gè)服務(wù)器,并且依賴于服務(wù)器來管理其生命周期。如果服務(wù)器關(guān)閉了,這種對(duì)象也就不存在,那么這個(gè) bean 的客戶端的信息也就不存在。
J2EE 應(yīng)用服務(wù)器為有狀態(tài)會(huì)話 bean 提供的故障轉(zhuǎn)移(failover)能夠解決一些問題,但是有狀態(tài)的解決方案沒有無狀態(tài)的解決方案易于擴(kuò)展。例如,在 WebSphere Application Server 中,對(duì)無狀態(tài)會(huì)話 bean 的請(qǐng)求,是通過對(duì)部署無狀態(tài)會(huì)話的成員集群進(jìn)行平衡加載來實(shí)現(xiàn)。相反地,J2EE 應(yīng)用服務(wù)器不能對(duì)有狀態(tài) bean 的請(qǐng)求進(jìn)行平衡加載。這意味著您的集群中的服務(wù)器的加載過程會(huì)是不均衡的。此外,使用有狀態(tài)會(huì)話 bean 將會(huì)再添加一些狀態(tài)到您的應(yīng)用服務(wù)器上,這也是不好的做法。這樣就增加了系統(tǒng)的復(fù)雜性,并且在出現(xiàn)故障的情況下使問題變得復(fù)雜化。創(chuàng)建健壯的分布式系統(tǒng)的一個(gè)關(guān)鍵原則是盡量使用無狀態(tài)行為。
因此,我們建議對(duì)大多數(shù)應(yīng)用程序使用無狀態(tài)會(huì)話 bean 方法。任何在處理時(shí)需要使用的與用戶相關(guān)的狀態(tài)應(yīng)該以參數(shù)的形式傳送到 EJB 的方法中(并且通過使用一種機(jī)制如 HttpSession 來存儲(chǔ)它)或者從持久性的后端存儲(chǔ)(例如通過使用實(shí)體 bean)作為 EJB 事務(wù)的一部分來進(jìn)行檢索。在合適的情況下,這個(gè)信息可以緩存到內(nèi)存中,但是要注意在分布式的環(huán)境中保存這種緩存所潛在的挑戰(zhàn)性。緩存非常適合于只讀數(shù)據(jù)。
總之,您要確保從一開始就要考慮到可伸展性。檢查設(shè)計(jì)中的所有設(shè)想,并且考慮到當(dāng)您的應(yīng)用程序要在多個(gè)服務(wù)器上運(yùn)行時(shí),是否也可以正常運(yùn)行。這個(gè)規(guī)則不但適合上述情況的應(yīng)用程序代碼,也適用于如 MBean 和其他管理界面的情況下。
避免使用有狀態(tài)性不只是對(duì) IBM/WebSphere 的建議,這是一個(gè)基本的 J2EE 設(shè)計(jì)原則。
8. 使用容器管理的事務(wù)。
學(xué)習(xí)一下 J2EE 中的兩階段提交事務(wù),并且使用這種方式,而不是開放您自己的事務(wù)管理。容器在事務(wù)優(yōu)化方面幾乎總是比較好的。
使用容器管理的事務(wù)(CMT)提供了兩個(gè)關(guān)鍵的優(yōu)勢(shì)(如果沒有容器支持這幾乎是不可能的):可組合的工作單元和健壯的事務(wù)行為。
如果您的應(yīng)用程序代碼顯式地使用了開始和結(jié)束事務(wù)(也許使用 javax.jts.UserTransaction 或者甚至是本地資源事務(wù)),而將來的要求需要組合模塊(也許會(huì)是代碼重構(gòu)的一部分),這種情況下往往需要改變事務(wù)代碼。例如,如果模塊 A 開始了一個(gè)數(shù)據(jù)庫(kù)事務(wù),更新數(shù)據(jù)庫(kù),隨后提交事務(wù),并且有模塊 B 做出同樣的處理,請(qǐng)考慮一下當(dāng)您在模塊 C 中嘗試使用上述兩個(gè)模塊,會(huì)出現(xiàn)什么情況呢?現(xiàn)在,模塊 C 正在執(zhí)行一個(gè)邏輯動(dòng)作,而這個(gè)動(dòng)作實(shí)際上將調(diào)用兩個(gè)獨(dú)立的事務(wù)。如果模塊 B 在執(zhí)行中失敗了,而模塊 A 的事務(wù)仍然能被提交。這是我們所不希望出現(xiàn)的行為。如果,相反地,模塊 A 和模塊 B 都使用 CMT 的話,模塊 C 也可以開始一個(gè) CMT(通常通過配置描述符),并且在模塊 A 和模塊 B 中的事務(wù)將是同一個(gè)事務(wù)的隱含部分,這樣就不再需要復(fù)雜的重寫代碼的工作了。
如果您的應(yīng)用程序在同一個(gè)操作中需要訪問多種資源,您就要使用兩階段提交事務(wù)。例如,如果從 JMS 隊(duì)列中刪除一個(gè)消息,并且隨后更新基于這條消息的紀(jì)錄,這時(shí),要保證這兩個(gè)操作都會(huì)執(zhí)行或都不會(huì)執(zhí)行就變得尤為重要。如果一條消息已經(jīng)從隊(duì)列中被刪除,而系統(tǒng)沒有更新與此消息相關(guān)的數(shù)據(jù)庫(kù)中的紀(jì)錄,那么這種系統(tǒng)是不穩(wěn)定的。一些嚴(yán)重的客戶及商業(yè)糾紛源自不一致的狀態(tài)。
我們時(shí)?吹揭恍┛蛻魬(yīng)用程序試圖實(shí)現(xiàn)他們自己的解決方案。也許會(huì)通過應(yīng)用程序的代碼在數(shù)據(jù)庫(kù)更新失敗的時(shí)候 "撤銷"對(duì)隊(duì)列的操作。我們不提倡這樣做。這種實(shí)現(xiàn)要比您最初的想象要復(fù)雜得多,并且還有許多其他的情況(想象一下如果應(yīng)用程序在執(zhí)行此操作的過程中突然崩潰的情況)。作為替代的方式,應(yīng)該使用兩階段提交事務(wù)。如果您使用 CMT,并且在一個(gè)單一的 CMT 中訪問兩階段提交的資源(例如 JMS 和大多數(shù)數(shù)據(jù)庫(kù)),WebSphere 將會(huì)處理所有的復(fù)雜工作。它將確保整個(gè)事務(wù)被執(zhí)行或者都不被執(zhí)行,包括系統(tǒng)崩潰、數(shù)據(jù)庫(kù)崩潰或其他的情況。其實(shí)現(xiàn)在事務(wù)日志中保存著事務(wù)狀態(tài)。當(dāng)應(yīng)用程序訪問多種資源的時(shí)候,我們?cè)趺磸?qiáng)調(diào)使用 CMT 事務(wù)的必要性都不為過。
9. 將 JSP 作為表示層的首選。
只有在需要多種表示輸出類型,并且輸出類型被一個(gè)單一的控制器及后端支持時(shí)才使用 XML/XSLT。
我們常聽到一些爭(zhēng)論說,為什么您選擇 XML/XSLT 而不是 JSP 作為表示層技術(shù)。選擇 XML/XSLT 的人的觀點(diǎn)是,JSP" 允許您將模型和視圖混合在一起",而 XML/XSLT 不會(huì)有這種問題。遺憾的是,這種觀點(diǎn)并不完全正確,或者至少不像白與黑那樣分的清楚。實(shí)際上,XSL 和 XPath 是編程語(yǔ)言。XSL 是圖靈完成的(Turing-complete),盡管它不符合大多數(shù)人定義的編程語(yǔ)言,因?yàn)樗腔谝?guī)則的,并且不具備程序員習(xí)慣的控制工具。
現(xiàn)在的問題是既然給予了這種靈活性,開發(fā)人員就會(huì)利用這種靈活性。盡管每個(gè)人都認(rèn)同 JSP 使開發(fā)人員容易在視圖中加入"類似模型"的行為,而實(shí)際上,在 XSL 中也有可能做出一些同樣的事情。盡管從 XSL 中進(jìn)行訪問數(shù)據(jù)庫(kù)這樣的事情會(huì)非常困難,但是我們?cè)?jīng)見到過一些異常復(fù)雜的 XSLT 樣式表執(zhí)行復(fù)雜的轉(zhuǎn)換,這實(shí)際上是模型代碼。
然而,應(yīng)該選擇 JSP 作為首選的表示技術(shù)的最基本的原因是,JSP 是現(xiàn)在支持最廣泛的、也是最被廣泛理解的 J2EE 視圖技術(shù)。而隨著自定義標(biāo)記庫(kù)、JSTL 和 JSP2.0 的新特性的引入,創(chuàng)建 JSP 變得更加容易,并且不需要任何 Java 代碼,以及可以將模型和視圖清晰的分離開。在一些開發(fā)環(huán)境中(如 WebSphere Studio)加入了對(duì) JSP(包括對(duì)調(diào)試的支持)的強(qiáng)大支持,并且許多開發(fā)人員發(fā)現(xiàn)使用 JSP 進(jìn)行開發(fā)要比使用 XLS 簡(jiǎn)單,一些支持 JSP 的圖形設(shè)計(jì)工具及其他特征(尤其在 JSF 這樣的框架下)使得開發(fā)人員可以以所見即所得的方式進(jìn)行 JSP 的開發(fā),而對(duì)于 XSL 有時(shí)不容易做到。
最后一個(gè)要謹(jǐn)慎考慮使用 JSP 的原因是速度問題。在 IBM 所作的對(duì)比 XSL 和 JSP 相對(duì)速度的性能測(cè)試顯示:在大多數(shù)情況下,JSP 在生成同樣的 HTML 的時(shí)候,要比 XSL 快好幾倍,甚至使用編譯過的 XSL 也是如此。盡管多數(shù)情況下這不是問題,但在性能要求很高的情況下,這就會(huì)成為問題。
然而,這也不能說,您永遠(yuǎn)也不要使用 XSL。在一些情況下,XSL 能夠表示一組固定的數(shù)據(jù),并且可以基于不同的樣式表來以不同的方式顯示這些數(shù)據(jù)的能力是顯示視圖的最佳解決方案。然而,這只是一種例外的情況,而不是通用的規(guī)則。如果您只是生成 HTML 來表達(dá)每一個(gè)頁(yè)面,那么在大多數(shù)情況下,XSL 是一種不必要的技術(shù),并且,它給您的開發(fā)人員所帶來的問題遠(yuǎn)比它所能解決的問題多。
10. 當(dāng)使用 HttpSession 時(shí),盡量只將當(dāng)前事務(wù)所需要的狀態(tài)保存其中,其他內(nèi)容不要保存在 HttpSession 中。
啟用會(huì)話持久性。
HttpSessions 對(duì)于存儲(chǔ)應(yīng)用程序狀態(tài)信息是非常有用的。其 API 易于使用和理解。遺憾的是,開發(fā)人員常常遺忘了 HttpSession 的目的----用來保持暫時(shí)的用戶狀態(tài)。它不是任意的數(shù)據(jù)緩存。我們已經(jīng)見到過太多的系統(tǒng)為每個(gè)用戶的會(huì)話放入了大量的數(shù)據(jù)(達(dá)到兆字節(jié))。那好了,如果同時(shí)有 1000 個(gè)登錄系統(tǒng)的用戶,每個(gè)用戶擁有 1MB 的會(huì)話數(shù)據(jù),那么就需要 1G 或者更多的內(nèi)存用于這些會(huì)話。要使這些 HTTP 會(huì)話數(shù)據(jù)較小一些,不然的話,您的應(yīng)用程序的性能將會(huì)下降。一個(gè)大約比較合適的數(shù)據(jù)量應(yīng)該是每個(gè)用戶的會(huì)話數(shù)據(jù)在 2K-4K 之間,這不是一個(gè)硬性的規(guī)則,8K 仍然沒有問題,但是顯然會(huì)比 2K 時(shí)的速度要慢。一定要注意,不要使 HttpSession 變成數(shù)據(jù)堆積的場(chǎng)所。
一個(gè)常見的問題是使用 HttpSession 緩存一些很容易再創(chuàng)建的信息,如果有必要的話。由于會(huì)話是持久性的,進(jìn)行不必要的序列化以及寫入數(shù)據(jù)是一種很奢侈的決定。相反地,應(yīng)該使用內(nèi)存中的哈希表來緩存數(shù)據(jù),并且在會(huì)話中保存一個(gè)對(duì)此數(shù)據(jù)進(jìn)行引用的關(guān)鍵字。這樣,如果不能成功登錄到另外的應(yīng)用服務(wù)器的話,就可以重新創(chuàng)建數(shù)據(jù)。
當(dāng)談及會(huì)話持久性時(shí),不要忘記要啟用這項(xiàng)功能。如果您沒有啟用會(huì)話持久性,或者服務(wù)器因?yàn)槟撤N原因停止了(服務(wù)器故障或正常的維護(hù)),則所有此應(yīng)用服務(wù)的當(dāng)前用戶的會(huì)話將會(huì)丟失。這是件令人非常不高興的事情。用戶不得不重新登錄,并且重新做一些他們?cè)?jīng)已經(jīng)做過的事情。相反地,如果啟用了會(huì)話持久性,WebSphere 會(huì)自動(dòng)將用戶(以及他們的會(huì)話)移到另外一個(gè)應(yīng)用服務(wù)器上去。用戶甚至不知道會(huì)有這種事情的發(fā)生。我們?cè)?jīng)見到過一些產(chǎn)品系統(tǒng)因?yàn)榇嬖谟诒镜卮a中令人難以忍受的 bug(不是 IBM 的代碼。┒蝗槐罎⒌那闆r,這這種情況下,上述功能仍然可以運(yùn)行良好。
11. 在 WebSphere 中,使用動(dòng)態(tài)緩存,并使用 WebSphere servlet 緩存機(jī)制。
通過使用這些功能,系統(tǒng)性能可以得到很大的提高,而開銷是很小的。并且不影響編程模型。
通過緩存來提高性能的好處是眾所周知的事情。遺憾的是,當(dāng)前的 J2EE 規(guī)范沒有包括一種用于 servlet/JSP 緩存的機(jī)制。然而,WebSphere 提供了對(duì)頁(yè)面以及片斷緩存的支持,這種支持是通過其動(dòng)態(tài)緩存功能來實(shí)現(xiàn)的,并且不需要對(duì)應(yīng)用程序作出任何改變。其緩存的策略是聲明性的,而且其配置是通過 XML 配置描述符來實(shí)現(xiàn)的。因此,您的應(yīng)用程序不會(huì)受到影響,并保持與 J2EE 規(guī)范的兼容性和移植性,同時(shí)還從 WebSphere 的 servlet 及 JSP 的緩存機(jī)制中得到性能的優(yōu)化。
從 servet 及 JSP 的動(dòng)態(tài)緩存機(jī)制得到的性能的提高是顯而易見的,這取決于應(yīng)用程序的特性。Cox 和 Martin [Cox] 指出對(duì)一個(gè)現(xiàn)有的 RDF(資源描述格式)站點(diǎn)摘要 (RSS)servlet,當(dāng)使用動(dòng)態(tài)緩存時(shí),其性能可以提高 10%。請(qǐng)注意這個(gè)實(shí)驗(yàn)只涉及到一個(gè)簡(jiǎn)單的 servlet,這個(gè)性能的增長(zhǎng)量可能并不能反映一個(gè)復(fù)雜的應(yīng)用程序。
為了更多地提高性能,將 WebSphere servlet/JSP 結(jié)果緩存與 WebSphere 插件 ESI Fragment 處理器、IBM HTTP Server Fast Response Cache Accelerator (FRCA) 和 Edge Server 緩存功能集成在一起。對(duì)于繁重的基于讀取的工作負(fù)荷,通過使用這些功能可以得到許多額外的好處。
12. 為了提高程序員的工作效率,將 CMP 實(shí)體 bean 作為 O/R 映射的首選解決方案.
通過 WebSphere 框架(readahead、緩存、隔離級(jí)別等)優(yōu)化性能。如果可能,有選擇的應(yīng)用一些模式來達(dá)到提高性能的目的,例如 Fast-Lane 閱讀器 [Marinescu]。
對(duì)象/關(guān)系(O/R)映射是使用 Java 創(chuàng)建企業(yè)級(jí)的應(yīng)用程序的基礎(chǔ)。幾乎每個(gè) J2EE 應(yīng)用程序都需要一些類型的 O/R 映射。J2EE 廠商提供一種 O/R 映射機(jī)制,這種機(jī)制在不同的廠商間是可移植的,高效的,并且能夠被一些標(biāo)準(zhǔn)及工具很好地支持。這就是 EJB 規(guī)范中的 CMP(容器管理的持久性)部分。
早期的 CMP 實(shí)現(xiàn)以表現(xiàn)不佳及不支持許多 SQL 結(jié)構(gòu)而著稱。然而,隨著 EJB 2.0 及 2.1 規(guī)范的出現(xiàn),以及被一些廠商所采納,并且隨著像 IBM WebSphere Studio Application Developer 的出現(xiàn),這些問題已經(jīng)不再是問題了。
CMP EJB 組件現(xiàn)在已經(jīng)被廣泛地應(yīng)用于許多高性能的應(yīng)用程序中。WebSphere 包括一些優(yōu)化功能以提高 EJB 組件的性能,優(yōu)化功能包括:對(duì)生命周期的緩存和 read-ahead 能力。這兩者優(yōu)化功能都是配置時(shí)的選項(xiàng),并且不需要對(duì)應(yīng)用程序進(jìn)行修改或者影響可移植性。
處于緩存狀態(tài)的生命周期緩存 CMP 狀態(tài)數(shù)據(jù)并提供基于時(shí)間的無效性。從處于緩存狀態(tài)的生命周期得到的性能提高可以達(dá)到選項(xiàng) A 的緩存性能,并且仍然可以為您的應(yīng)用程序提供可伸展性。Read-ahead 能力和容器管理的關(guān)系結(jié)合使用。這個(gè)特性通過在相同的查詢中隨意地檢索相關(guān)的數(shù)據(jù)作為父數(shù)據(jù)而減少與數(shù)據(jù)庫(kù)的交互。如果相關(guān)的數(shù)據(jù)要通過使用并發(fā)的查詢來訪問的話,這種方法可以得到性能的改進(jìn)。[Gunther]提供了詳細(xì)的描述以及通過這些特性得到的性能提高的細(xì)節(jié)。
此外,為了完全優(yōu)化您的 EJB 組件,當(dāng)指定隔離級(jí)別時(shí)要特別注意。盡可能使用最低的隔離級(jí)別,并且仍然保持您的數(shù)據(jù)的完整性。較低的隔離級(jí)別可以提供最佳的性能,并且可以降低出現(xiàn)數(shù)據(jù)庫(kù)死鎖的危險(xiǎn)。
這是目前最有爭(zhēng)議的最佳實(shí)踐。已經(jīng)有大量的文章贊揚(yáng) CMP EJB,同樣的貶斥聲也不絕于耳。然而,這里最基本的問題是數(shù)據(jù)庫(kù)開發(fā)是困難的。當(dāng)您開始使用任何持久性解決方案之前,您需要掌握查詢以及數(shù)據(jù)庫(kù)鎖定如何工作這些基礎(chǔ)知識(shí)。如果您選擇使用 CMP EJB,您要確保您已經(jīng)通過一些書籍(例如 [Brown] 和 [Barcia])知道如何使用它們。在鎖定及爭(zhēng)用方面有一些微妙的交互難以理解,但是,在您耗費(fèi)一定的時(shí)間及努力后會(huì)將其掌握的。
結(jié)束語(yǔ)
在這個(gè)簡(jiǎn)短的摘要中,我們已經(jīng)向您介紹了 J2EE 中的核心模式和最佳實(shí)踐,這使得 J2EE 開發(fā)成為一種可管理的過程。盡管我們并沒有給出所有在實(shí)踐中使用這些模式的必要細(xì)節(jié),但是我們希望能夠給您足夠的指點(diǎn)和指導(dǎo),以幫助您決定下一步要做什么。