引言
在過去的幾乎整整十年中,人們編寫了很多有關 Java™ Platform, Enterprise Edition (Java EE) 最佳實踐的內容。現在有十多本書籍 和數以百計(可能更多)的文章,提供了關於應該如何編寫 Java EE 應用程序的 見解。事實上,這方面的參考資料如此之多,並且這些參考資料之間往往還存在 著一些矛盾的建議,以至於在這些混雜的內容中進行學習本身也成為了采用 Java EE 的障礙。因此,為了給剛進入這個領域的客戶提供一些簡單的指導,我們匯編 了這個最重要的最佳實踐列表,其中包括我們認為最重要和最有效的 Java EE 最 佳實踐。遺憾的是,我們無法僅在 10 大最佳實踐中描述所有需要介紹的內容。 因此,為了避免遺漏關鍵的最佳實踐和尊重 Java EE 的發展,我們的列表中包含 了“19 大”關鍵的 Java EE 最佳實踐。
1. 始終使用 MVC 框 架。
將業務邏輯(Java Bean 和 EJB 組件)從控制器邏輯 (Servlet/Struts 操作)和表示邏輯(JSP、XML/XSLT)中清晰地分離出來。良 好的分層可以帶來許多好處。
這項實踐非常重要,以致沒有其他最佳實踐 可以與其相提並論。對於良好的 Java EE 應用程序設計而言,模型-視圖-控制器 (MVC) 是至關重要的。它將程序的任務簡單地分為下面幾個部分:
負責業 務邏輯的部分(模型,通常使用 Enterprise JavaBeans™ 或傳統 Java 對 象來實現)。
負責用戶接口表示的部分(視圖)。
負責應用程序 導航的部分(控制器,通常使用 Java Servlet 或類 Struts 控制器這樣相關的 類來實現)。
對於 Java EE,有許多關於這個主題的優秀評論,我們特別 推薦感興趣的讀者可以參考 [Fowler] 或者 [Brown](請參見參考資料部分)的 評論,以便全面和深入地了解相關內容。
如果不遵循基本的 MVC 體系結 構,在開發過程中就會出現許多的問題。最常見的問題是,將過多的任務放到該 體系結構的視圖部分中。可能存在使用 JSP 標記來執行數據庫訪問,或者在 JSP 中進行應用程序的流程控制,這在小規模的應用程序中是比較常見的,但是,隨 著後期的開發,這樣做將會帶來問題,因為 JSP 逐步變得越來越難以維護和調試 。
類似地,我們也經常看到將視圖層構建到業務邏輯的情況。例如,一個 常見的問題就是將在構建視圖時使用的 XML 解析技術直接應用到業務層。業務層 應該對業務對象進行操作,而不是對與視圖相關的特定數據表示進行操作。
然而,僅僅使用適當的組件無法實現應用程序的正確分層。我們常常見到 一些應用程序包含 Servlet、JSP 和 EJB 組件所有這三項,然而,其主要的業務 邏輯卻是在 Servlet 層實現的,或者應用程序導航是在 JSP 中處理的。您必須 對代碼進行嚴格的檢查和重構,以確保僅在模型層中處理業務邏輯,在控制器層 中進行應用程序導航,而視圖應該只關心如何將模型對象呈現為合適的 HTML 和 Javascript™。
本文中這項建議的涵義應該比原始版本中的更加清 楚。用戶接口技術不斷地發生著變化,將業務邏輯關聯於用戶接口,會使得對接 口的更改影響到現有的系統。幾年之前,Web 應用程序用戶接口開發人員可能從 Servlet 和 JSP、Struts 和 XML/XSL 轉換中進行選擇。在那以後,Tiles 和 Faces 非常流行,而現在,AJAX 大行其道。如果每當首選的用戶接口技術發生了 更改就要重新開發應用程序的核心業務邏輯,那麼就糟透了。
2. 不要做 重復的工作。
使用常見的、經過證實的框架,如 Apache Struts、 JavaServer Faces 和 Eclipse RCP。使用經過證實的模式。
回到我們開 始幫助客戶使用剛出現的 Java EE 標准的時候,我們發現(和許多其他人一樣) ,通過直接使用基礎的 Servlet 和 JSP 規范構建 UI 應用程序來開發用戶接口 開發框架,可以極大地提高開發人員工作效率。因此,許多公司開發了他們自己 的 UI 框架,這些框架可以簡化接口開發的任務。
隨著開放源碼的框架( 如 Apache Struts)的出現 [Brown],我們相信,可以自動地和快速地轉換到這 些新的框架。我們認為,使用開放源碼社區支持的框架非常適合於開發人員,並 且這些框架很快得到了廣泛認可,不僅可用於新的開發,還可以修改現有的應用 程序。
但令人感到奇怪的是,事實並非如此。我們仍可以看到許多公司在維護或甚至 開發新的用戶接口框架,而這些框架的功能與 Struts 或者 JSF 是完全相同的。 之所以會出現這種情況,有許多原因:機構惰性,“非我發明”症, 不了解更改現有代碼的好處、或者甚至傲慢地認為能夠比開放源碼開發人員的特 定框架做得更好。
然而,這些原因都已經過時了,不能夠成為不采用標准 框架的借口。Struts 和 JSF 不僅在 Java 社區中得到了廣泛認可,而且還受到 WebSphere 運行時和 Rational® 工具套件的全面支持。同樣地,在富客戶端 領域中,Eclipse RCP(富客戶端平台,Rich Client Platform)獲得了廣泛的認 可,可用於構建獨立的富客戶端。盡管不是 Java EE 標准中的一部分,但這些框 架現在已成為 Java EE 社區的一部分,並且理應如此。
對於那些因為傲 慢而不願使用現成的 UI 框架的人,應該閱讀 [Alur] 和 [Fowler] 中介紹的內 容。這兩本書詳細地描述了企業 Java 應用程序中最常用的可重用模式。從類似 於會話 Facade 這樣簡單的模式(將在後面的建議中討論)到類似於 Fowler 持 久性模式(許多開放源碼的持久性框架對其進行了實現)這樣比較復雜的模式, 其中的內容體現了 Java 前輩們所積累的智慧。那些不能吸取教訓的人必定會重 蹈覆轍(如果他們非常幸運,能夠在第一次失敗之後獲得重來一次的機會),他 們不得不向哲學家 Santayana 說抱歉。
3. 在應用程序的每一層都使用自 動單元測試和測試管理。
不要只是測試您的圖形用戶界面(GUI)。分層 的測試使得調試和維護工作變得極其簡單。
在過去的幾年中,在方法學領 域有了相當大的革新,例如新出現的被稱為 Agile(如參考資料部分中的 SCRUM [Schwaber] 和極限編程 [Beck1])的輕量級方法現在已經得到了很普遍的應用。 幾乎所有的這些方法中的一個共同特征是它們都提倡使用自動的測試工具,這些 工具可以幫助開發人員用更少的時間進行回歸測試,並可以幫助他們避免由於不 充分的回歸測試造成的錯誤,因此可以用來提高程序員的工作效率。實際上,還 有一種被稱為 Test-First Development [Beck2] 的方法,這種方法甚至提倡在 開發實際的代碼之前就先編寫單元測試。然而,在您測試代碼之前,您需要將代 碼分割成一些可測試的片斷。一個“大泥球”是難以測試的,因為它 不是只實現一個簡單的易於識別的功能。如果您的每個代碼片斷實現多個方面的 功能,將難以測試其中的每個部分以保證其正確性。
MVC 體系結構(以及 Java EE 中的 MVC 實現)的一個優點就是元素的組件化能夠(實際上,相當的簡 單)對您的應用程序進行單元測試。因此,您可以方便地對實體 Bean、會話 Bean 以及 JSP 獨立編寫測試用例,而不必考慮其他代碼。現在有許多用於 Java EE 測試的框架和工具,這些框架及工具使得這一過程更加簡單。例如,JUnit( 是一種由 junit.org 開發的開放源代碼工具)和 Cactus(由 Apache 協會開發 的開放源代碼工具)對於測試 Java EE 組件都非常有用。[Hightower] 詳細探討 了如何在 Java EE 中使用這些工具。
盡管所有這些詳述了怎樣徹底地測 試您的應用程序,但是我們仍然看到一些人認為只要他們測試了 GUI(可能是基 於 Web 的 GUI,或者是獨立的 Java 應用程序),則他們就全面地測試了整個應 用程序。僅進行 GUI 測試是不夠的。GUI 測試很難達到全面的測試,有以下幾種 原因。
使用 GUI 測試很難徹底地測試到系統的每一條路徑,GUI 僅僅是 影響系統的一種方式。可能存在後台運算、腳本和各種各樣的其他訪問點,這也 需要進行測試,然而,它們通常並不具有 GUI。
GUI 級的測試是一種非常 粗粒度的測試。這種測試只是在宏觀水平上測試系統的行為,這意味著一旦發現 存在問題,則與此問題相關的整個子系統都要進行檢查,這使得找出錯誤將是非 常困難的事情。
GUI 測試通常只有在整個開發周期的後期才能很好地得到 測試,這是因為只有這那個時候 GUI 才得到完整的定義。這意味著只有在後期才 可能發現潛在的錯誤。
一般的開發人員可能沒有自動的 GUI 測試工具。 因此,當一個開發人員對代碼進行更改時,沒有一種簡單的方法來重新測試受到 影響的子系統。這實際上不利於進行良好的測試。如果開發人員具有自動的代碼 級單元測試工具,開發人員就能夠很容易地運行這些工具以確保所做的更改不會 破壞已經存在的功能。
如果添加了自動構建功能,則在自動構建過程中添加一個自動的單元測試工具 是非常容易的事情。當完成這些設置以後,整個系統就可以有規律地進行重建, 並且回歸測試幾乎不需要人的參與。
另外,我們必須強調,使用 EJB 和 Web 服務進行分布式的、基於組件的開發 使得測試單個組件變得非常必要。如果沒有“GUI”需要測試,您就必須進行低級 (lower-level)測試。最好以這種方式開始測試,省得當您將分布式的組件或 Web 服務作為您的應用程序的一部分時,您不得不花費心思重新進行測試。
總之,通過使用自動的單元測試,能夠很快地發現系統的缺陷,並且也易於發 現這些缺陷,使得測試工作變得更加系統化,因此整體的質量也得以提高。
4. 按照規范來進行開發,而不是按照應用服務器來進行開發。
要將規范熟記於心,如果要背離規范,需經過慎密的考慮後才可以這樣做。這 是因為當您背離規則的時候,您所做的事情往往並不是您應該做的事情。
當您要背離 Java EE 允許您做的事情的時候,這很容易讓使您遭受不幸。我 們發現有一些開發人員鑽研一些 Java EE 允許之外的東西,他們認為這樣做可以 “稍微”改善 Java EE 的性能,而他們最終只會發現這樣做會引起嚴重的性能問 題,或者在以後的移植(從一個廠商到另一個廠商,或者是更常見的從一個版本 到另一個版本)中會出現問題。實際上,這種移植問題是如此嚴重,以致 [Beaton] 將此原則稱為移植工作的基本最佳實踐。
現在有好幾個地方如果不直接使用 Java EE 提供的方法肯定會產生問題。一 個常見的例子就是開發人員通過使用 JAAS 模塊來替代 Java EE 安全性,而不是 使用內置的遵循規范的應用服務器機制來進行驗證和授權。要注意不要脫離 Java EE 規范提供的驗證機制。如果脫離了此規范,這將是系統存在安全漏洞以及廠商 兼容性問題的主要原因。類似地,要使用 Servlet 和 EJB 規范提供的授權機制 ,並且如果您要偏離這些規范的話,要確保使用規范定義的 API(例如 getCallerPrincipal())作為實現的基礎。通過這種方式,您將能夠利用廠商提 供的強安全性基礎設施,其中,業務要求需要支持復雜的授權規則。(有關授權 的更詳細內容,請參見 [Ilechko]。)
其他常見的問題包括使用不遵循 Java EE 規范的持久性機制(這使得事務管 理變得困難)、在Java EE程序中使用不適當的 J2SE 方法(例如線程或 singleton),以及使用您自己的方法解決程序到程序(program-to-program)的 通信,而不是使用 Java EE 內在支持的機制(例如 JCA、JMS 或 Web 服務)。 當您將一個遵循 Java EE 的服務器移植到其他的服務器上,或者移植到相同服務 器的新版本上,上述的設計選擇將會造成無數的問題。使用 Java EE 之外的元素 ,通常會導致一些細微的可移植性問題。唯一要背離規范的情況是,當一個問題 在規范的范圍內無法解決的時候。例如,安排執行定時的業務邏輯在 EJB2.1 出 現之前是一個問題。在類似這樣的情況下,我們建議當有廠商提供的解決方案時 就使用廠商提供的解決方案(例如 WebSphere Application Server Enterprise 中的 Scheduler 工具),而在沒有廠商提供的解決方案時就使用第三方提供的工 具。當然,現在的 EJB 規范提供了基於時間的函數,所以我們鼓勵使用這些標准 接口。如果使用廠商提供的解決方案,應用程序的維護以及將其移植到新的規范 版本將是廠商的問題,而不是您的問題。
最後,要注意不要太早地采用新技術。太過於熱衷采用還沒有集成到 Java EE 規范的其他部分或者還沒有集成到廠商的產品中的技術常會帶來災難性的後果。 支持是關鍵的——如果您的廠商不直接支持某種特定的技術,那麼您在采用此技 術時就應該非常謹慎。有些人(尤其是開發人員)過分關注於簡化開發過程,忽 略了依賴大量本組織之外開發的代碼的長期後果,而供應商並不支持這些代碼。 我們發現,許多項目團隊沉迷於新技術(例如最新的開放源代碼框架),並很快 地依賴於它,卻沒有考慮它對業務帶來的實際代價。坦白地說,對於使用您的供 應商所提供的產品之外的任何技術的決策,都應該由企業組織結構中的各個部門 、業務團隊和法律團隊(或您的環境中的等同機構)仔細地進行評審,這與正常 的產品購買決策完全相同。畢竟,我們中的大多數人是在解決業務問題,而不是 推進技術的發展。
5. 從一開始就計劃使用 Java EE 安全性。
啟用 WebSphere 安全性。這使您的 EJB 和 URL 至少可以讓所有授權用戶訪 問。不要問為什麼——照著做就是了。
在與我們合作的客戶中,一開始就打算啟用 WebSphere Java EE 安全性的顧 客是非常少的,這一點一直讓我們感到吃驚。據我們估計大約只有 50% 的顧客一 開始就打算使用此特性。例如,我們曾與一些大型的金融機構(銀行、代理等等 )合作過,他們也沒有打算啟用安全性。幸運的是,這種問題在部署之前的檢查 時就得以解決。
不使用 Java EE 安全性是件危險的事情。假設您的應用程序需要安全性(幾 乎所有的應用程序都需要),敢打賭您的開發人員能夠構建出自己的安全性基礎 設施,其比您從 Java EE 廠商那裡買來的更好。這可不是個好的賭博游戲。為分 布式的應用程序提供安全性是異常困難的。例如,您需要使用網絡安全加密令牌 控制對 EJB 的訪問。以我們的經驗看來,大多數自己構建的安全性基礎設施是不 安全的,並且有重大的缺陷,這使產品系統極其脆弱。(有關更詳細的信息,請 參考 [Barcia] 的第 18 章。)
一些不使用 Java EE 安全性的理由包括:擔心性能的下降,相信其他的安全 性(例如 IBM Tivoli® Access Manager 和 Netegrity SiteMinder)可以 取代 Java EE 安全性,或者是不知道 WebSphere Application Server 安全特性 及功能。不要陷入這些陷阱之中。尤其是,盡管像 Tivoli Access Manager 這樣 的產品能夠提供優秀的安全特性,但是僅僅其自身不可能保護整個 Java EE 應用 程序。這些產品必須與 Java EE 應用服務器聯合起來才可能全面地保護您的系統 。
其他一種常見的不使用 Java EE 安全性的原因是,基於角色的模型沒有提供 足夠的粒度訪問控制以滿足復雜的業務規則。盡管事實是這樣的,但這也不應該 成為不使用 Java EE 安全性的理由。相反地,應該將 Java EE 驗證及 Java EE 角色與特定的擴展規則結合起來。如果復雜的業務規則需要做出安全性決策,那 就編寫相應的代碼,其安全性決策要基於可以直接使用的以及可靠的 Java EE 驗 證信息(用戶 ID 和角色)。(有關授權的更詳細的信息,請參見 [Ilechko]。 )
6. 創建您所知道的。
反復的開發工作將使您能夠逐漸地掌握所有的 Java EE 模塊。要從創建小 而簡單的模塊開始而不是從一開始就馬上涉及到所有的模塊。
我們必須承認 Java EE 是龐大的體系。如果一個開發團隊只是開始使用 Java EE,這將很難一下子就能掌握它。在 Java EE 中有太多的概念和 API 需要掌握 。在這種情況下,成功掌握 Java EE 的關鍵是從簡單的步驟開始做起。
這種方法可以通過在您的應用程序中創建小而簡單的模塊來得到最好的實現。 如果一個開發團隊通過創建一個簡單的域模型以及後端的持久性機制(也許使用 的是 JDBC),並且對其進行了完整的測試,這會增強他們的自信心,於是他們會 使用該域模型去掌握使用 Servlet 和 JSP 的前端開發。如果一個開發團隊發現 有必要使用 EJB,他們也會類似地開始在容器管理的持久性 EJB 組件之上使用簡 單的會話 Facade,或者使用基於 JDBC 的數據訪問對象(JDBC-based Data Access Objects,DAO),而不是跳過這些去使用更加復雜的構造(例如消息驅動 的 Bean 和 JMS)。
這種方法並不是什麼新方法,但是很少有開發團隊以這種方式來培養他們的技 能。相反地,多數開發團隊由於嘗試馬上就構建所有的模塊,同時涉及 MVC 中的 視圖層、模型層和控制器層,這樣做的結果是他們往往會陷入進度的壓力之中。 他們應該考慮一些敏捷(Agile)開發方法,例如極限編程(XP),這種開發方法 采用一種增量學習及開發方法。在 XP 中有一種稱為 ModelFirst [Wiki] 的過程 ,這個過程涉及到首先構建域模型作為一種機制來組織和實現用戶場景。基本說 來,您要構建域模型作為您要實現的用戶場景的首要部分,然後在域模型之上構 建一個用戶界面(UI)作為用戶場景實現的結果。這種方法非常適合讓一個開發 團隊一次只學到一種技術,而不是讓他們同時面對很多種情況(或者讓他們讀很 多書),這會令他們崩潰的。
還有,對每個應用程序層重復的開發可能會包含一些適當的模式及最佳實踐。 如果您從應用程序的底層開始應用一些模式(如數據訪問對象和會話 Facade), 您就不應該在您的JSP和其他視圖對象中使用域邏輯。
最後,當您開發一些簡單的模塊時,在開始的初期就可以對您的應用程序進行 性能測試。如果直到應用程序開發的後期才進行性能測試的話,這往往會出現災 難性的後果,正如 [Joines] 所述。
7. 當使用 EJB 組件時,始終使用會話 Facade。
在體系結構合適的情況下,使用本地 EJB。
當使用 EJB 組件時,使用會話 Facade 是一個確認無疑的最佳實踐。實際上 ,這個通用的實踐被廣泛地應用到任何分布式技術中,包括 CORBA、EJB 和 DCOM 。從根本上講,您的應用程序的分布“交叉區域”越是底層化,對小塊的數據由 於多次重復的網絡中繼造成的時間消耗就越少。要達到這個目的的方法是,創建 大粒度的 Facades 對象,這個對象包含邏輯子系統,因而可以通過一個方法調用 就可以完成一些有用的業務功能。這種方法不但能夠降低網絡開銷,而且在 EJB 內部通過為整個業務功能創建一個事務環境也可以大大地減少對數據庫的訪問次 數。[Alur] 對這種模式進行了規范的表示,[Fowler](並且包括除 EJB 之外的 情況)和 [Marinescu] 也對其進行了描述(請參見參考資料)。細心的讀者會發 現,這實際上正是面向服務的體系結構 (SOA) 中的核心原則之一。
EJB 本地接口(從 EJB 2.0 規范開始使用)為共存的 EJB 提供了性能優化方 法。本地接口必須被您的應用程序顯式地進行訪問,這需要代碼的改變和防止以 後配置 EJB 時需要應用程序的改變。如果您確定 EJB 調用始終是本地的,那麼 可以充分利用本地 EJB 的優化。然而,會話 Facade 本身的實現(典型例子如無 狀態會話 Bean)應該設計為遠程接口。通過這種方式,其他的客戶端可以遠程地 使用 EJB 本身,而不會破壞現有的業務邏輯。因為 EJB 可以同時具有本地和遠 程接口,所以這是完全可以實現的。
為了性能的優化,可以將一個本地接口添加到會話 Facade。這樣做利用了這 樣一個事實,在大多數情況下(至少在 Web 應用程序中),您的 EJB 客戶端和 EJB 會共同存在於同一個 Java 虛擬機(JVM)中。另外一種情況是,如果會話 Facade 在本地被調用,可以使用 Java EE 應用服務器配置優化(configuration optimizations),例如 WebSphere 中的“No Local Copies”。然而,您必須注 意到這些可供選擇的方案會將交互方法從按值傳遞(pass-by-value)改變為按引 用傳遞(pass-by-reference)。這可能會在您的代碼中產生很微妙的錯誤。最好 使用本地 EJB,因為對於每個 Bean 而言,其行為是可以控制的,而不會影響到 整個應用服務器。
如果在您的會話 Facade 中使用遠程接口(而不是本地接口),您也可以將同 樣的會話 Facade 在 Java EE 1.4 中以兼容的方式作為 Web 服務來配置。(這 是因為 JSR 109,Java EE 1.4 中的 Web 服務部署部分,要求使用無狀態會話 Bean 的遠程接口作為 EJB Web 服務和 EJB 實現的接口。)這樣做是值得的,因 為這樣做可以為您的業務邏輯增加客戶端類型的數量。
8. 使用無狀態會話 Bean,而不是有狀態會話 Bean。
這樣做可以使您的系統更經得起故障轉移。使用 HttpSession 存儲和用戶相 關的狀態。
以我們的觀點來看,有狀態會話 Bean 的概念已經過時了。如果您仔細對其考 慮一下,一個有狀態會話 Bean 實際上與一個 CORBA 對象在體系結構上是完全相 同的,無非就是一個對象實例綁定到一個服務器,並且依賴於服務器來管理其生 命周期。如果服務器關閉了,這種對象也就不存在,那麼這個 Bean 的客戶端的 信息也就不存在。
Java EE 應用服務器為有狀態會話 Bean 提供的故障轉移能夠解決一些問題, 但是有狀態的解決方案沒有無狀態的解決方案易於擴展。例如,在 WebSphere Application Server 中,對無狀態會話 Bean 的請求,是通過對部署無狀態會話 的成員集群進行平衡加載來實現。相反地,Java EE 應用服務器不能對有狀態 Bean 的請求進行平衡加載。這意味著您的集群中的服務器的加載過程會是不均衡 的。此外,使用有狀態會話 Bean 將會再添加一些狀態到您的應用服務器上,這 也是不好的做法。有狀態會話 Bean 增加了系統的復雜性,並且在出現故障的情 況下使問題變得復雜化。創建健壯的分布式系統的一個關鍵原則是盡量使用無狀 態行為。
因此,我們建議對大多數應用程序使用無狀態會話 Bean 方法。任何在處理時 需要使用的與用戶相關的狀態應該以參數的形式傳送到 EJB 的方法中(並且通過 使用一種機制如 HttpSession 來存儲它)或者從持久性的後端存儲(例如通過使 用實體 Bean)作為 EJB 事務的一部分來進行檢索。在合適的情況下,這個信息可 以緩存到內存中,但是要注意在分布式的環境中保存這種緩存所潛在的挑戰性。 緩存非常適合於只讀數據。
總之,您要確保從一開始就要考慮到可擴展性。檢查設計中的所有設想,並且 考慮到當您的應用程序要在多個服務器上運行時,是否也可以正常運行。檢查設 計中所有的假設,判斷如果您的應用程序運行於多個服務器之上,它們是否依然 成立。這個規則不但適合上述情況的應用程序代碼,也適用於如 MBean 和其他管 理接口的情況。
避免使用有狀態性不只是對 IBM/WebSphere 的建議,這是一個基本的 Java EE 設計原則。請參見 [Jewell] 的 Tyler Jewell 對有狀態 Bean 的批評,其觀 點和上述的觀點是相同的。
9. 使用容器管理的事務。
學習一下 Java EE 中的兩階段提交事務,並且使用這種方式,而不是開發您 自己的事務管理。容器在事務優化方面幾乎總是比較好的。
使用容器管理的事務(CMT)提供了兩個關鍵的優勢(如果沒有容器支持這幾 乎是不可能的):可組合的工作單元和健壯的事務行為。
如果您的應用程序代碼顯式地使用了開始和結束事務(也許使用 javax.jts.UserTransaction 或者甚至是本地資源事務),而將來的要求需要組 合模塊(也許會是代碼重構的一部分),這種情況下往往需要改變事務代碼。例 如,如果模塊 A 開始了一個數據庫事務,更新數據庫,隨後提交事務,並且有模 塊 B 做出同樣的處理,請考慮一下當您在模塊 C 中嘗試使用上述兩個模塊,會 出現什麼情況呢?現在,模塊 C 正在執行一個邏輯動作,而這個動作實際上將調 用兩個獨立的事務。如果模塊 B 在執行中失敗了,而模塊 A 的事務仍然能被提 交。這是我們所不希望出現的行為。如果,相反地,模塊 A 和模塊 B 都使用 CMT 的話,模塊 C 也可以開始一個 CMT(通常通過配置描述符),並且在模塊 A 和模塊 B 中的事務將是同一個事務的隱含部分,這樣就不再需要重寫復雜的代碼 了。
如果您的應用程序在同一個操作中需要訪問多種資源,您就要使用兩階段提交 事務。例如,如果從 JMS 隊列中刪除一個消息,並且隨後更新基於這條消息的紀 錄,這時,要保證這兩個操作都會執行或都不會執行就變得尤為重要。如果一條 消息已經從隊列中被刪除,而系統沒有更新與此消息相關的數據庫中的記錄,那 麼這種系統是不一致的。一些嚴重的客戶及商業糾紛源自不一致的狀態。
我們時常看到一些客戶應用程序試圖實現他們自己的解決方案。也許會通過應 用程序的代碼在數據庫更新失敗的時候“撤銷”對隊列的操作。我們不提倡這樣 做。這種實現要比您最初的想象復雜得多,並且還有許多其他的情況(想象一下 如果應用程序在執行此操作的過程中突然崩潰的情況)。作為替代的方式,應該 使用兩階段提交事務。如果您使用 CMT,並且在單一的 CMT 中訪問兩階段提交的 資源(例如 JMS 和大多數數據庫),WebSphere 將會處理所有的復雜工作。它將 確保整個事務被執行或者都不被執行,包括系統崩潰、數據庫崩潰或其他的情況 。其實現在事務日志中保存著事務狀態。當應用程序訪問多種資源的時候,我們 怎麼強調使用 CMT 事務的必要性都不為過。如果您所訪問的資源不支持兩階段提 交,那麼您當然就沒有別的選擇了,只能使用一種比較復雜的方法,但是您應該 盡量避免這種情況。
10. 將 JSP 作為表示層技術的首選。
只有在需要多種表示輸出類型,並且輸出類型被單一的控制器及後端支持時才 使用 XML/XSLT。
我們常聽到一些爭論說,為什麼您選擇 XML/XSLT 而不是 JSP 作為表示層技 術,因為 JSP“允許您將模型和視圖混合在一起”,而 XML/XSLT 不會有這種問 題。遺憾的是,這種觀點並不完全正確,或者至少不像白與黑那樣分的清楚。實 際上,XSL 和 XPath 是編程語言。事實上,XSL 是圖靈完備的(Turing- complete),盡管它不符合大多數人定義的編程語言,因為它是基於規則的,並 且不具備程序員習慣的控制工具。
問題是既然給予了這種靈活性,開發人員就會利用這種靈活性。盡管每個人都 認同 JSP 使開發人員容易在視圖中加入“類似模型”的行為,而實際上,在 XSL 中也有可能做出一些同樣的事情。盡管從 XSL 中進行訪問數據庫這樣的事情會非 常困難,但是我們曾經見到過一些異常復雜的 XSLT 樣式表執行復雜的轉換,這 實際上是模型代碼。
然而,應該選擇 JSP 作為首選的表示技術的最基本的原因是,JSP 是現在支 持最廣泛的、也是最被廣泛理解的 Java EE 視圖技術。而隨著自定義標記庫、 JSTL 和 JSP2.0 的新特性的引入,創建 JSP 變得更加容易,並且不需要任何 Java 代碼,以及可以將模型和視圖清晰地分離開。在一些開發環境中(如 IBM Rational Application Developer)加入了對 JSP(包括對調試的支持)的強大 支持,並且許多開發人員發現使用 JSP 進行開發要比使用 XSL 更加簡單,主要 是因為 JSP 是基於例程的,而不是基於規則的。盡管 Rational Application Developer 支持 XSL 的開發,但一些支持 JSP 的圖形設計工具及其他特征(尤 其在 JSF 這樣的框架下)使得開發人員可以以所見即所得的方式進行 JSP 的開 發,而使用 XSL 有時不容易做到。
然而,這並不表示您絕不 應該使用 XSL。在一些情況下,XSL 能夠表示一組 固定的數據,並且可以基於不同的樣式表(請參見 [Fowler])來以不同的方式顯 示這些數據的能力是顯示視圖的最佳解決方案。然而,這只是一種特殊的情況, 而不是通用的規則。如果您只是生成 HTML 來表達每一個頁面,那麼在大多數情 況下,XSL 是一種不必要的技術,並且,它給您的開發人員所帶來的問題遠比它 所能解決的問題多。
11. 當使用 HttpSession 時,盡量只將當前事務所需要的狀態保存在其中, 其他內容不要保存在 HttpSession 中。
啟用會話持久性。
HttpSessions 對於存儲應用程序狀態信息是非常有用的。其 API 易於使用和 理解。遺憾的是,開發人員常常遺忘了 HttpSession 的目的——用來保持臨時的 用戶狀態。它不是任意的數據緩存。我們已經見到過太多的系統為每個用戶的會 話放入了大量的數據(達到兆字節)。如果同時有 1000 個登錄系統的用戶,每 個用戶擁有 1MB 的會話數據,那麼就需要 1G 或者更多的內存用於這些會話。保 持這些 HTTP 會話數據較小。不然的話,您的應用程序的性能將會下降。一個大 約比較合適的數據量應該是每個用戶的會話數據在 2K-4K 之間。這不是一個硬性 的規則。8K 仍然沒有問題,但是顯然會比 2K 時的速度要慢。一定要注意,不要 使 HttpSession 變成數據堆積的場所。
一個常見的問題是使用 HttpSession 緩存一些很容易再創建的信息,如果有 必要的話。由於會話是持久性的,進行不必要的序列化以及寫入數據是一種很奢 侈的決定。相反地,應該使用內存中的哈希表來緩存數據,並且在會話中保存一 個對此數據進行引用的鍵。這樣,如果不能成功登錄到另外的應用服務器的話, 就可以重新創建數據。(有關更詳細的信息,請參見 [Brown2]。)
當談及會話持久性時,不要忘記要啟用這項功能。如果您沒有啟用會話持久性 ,或者服務器因為某種原因停止了(服務器故障或正常的維護),則所有此應用 服務器的當前用戶的會話將會丟失。這是件令人非常掃興的事情。用戶不得不重 新登錄,並且重新做一些他們曾經已經做過的事情。相反地,如果啟用了會話持 久性,WebSphere 會自動將用戶(以及他們的會話)移到另外一個應用服務器上 去。用戶甚至不知道發生了這樣的事情。我們曾經見到過一些產品系統,因為本 地代碼中存在令人難以忍受的錯誤(不是 IBM 的代碼!)而經常崩潰,在這種情 況下,上述功能仍然可以運行良好。
12. 充分利用應用服務器中不需要修改代碼的特性。
使用某些特性(如 WebSphere Application Server 緩存和 Prepared Statement 緩存)可以極大地提高性能,並且使得開銷最小。
前面的最佳實踐 4 清楚地描述了這樣一種案例,即關於為什麼應該謹慎的使 用可能修改代碼的應用服務器特定的特性。它使得難以實現可移植性,並且可能 給版本的遷移帶來困難。然而,特別是在 WebSphere Application Server 中, 有一套應用服務器特定的特性,您可以並且應該充分地利用它們,因為它們不會 修改您的代碼。您應該按照規范來編寫代碼,但如果您了解這些特性以及如何正 確地使用它們,那麼您就可以利用它們顯著地改善性能。
作為這個最佳實踐的一個示例,在 WebSphere Application Server 中,您應 該開啟動態緩存,並且使用 Servlet 緩存。系統性能可以得到很大的提高,而開 銷是最小的,並且不影響編程模型。通過緩存來提高性能的好處是眾所周知的事 情。遺憾的是,當前的 Java EE 規范沒有包括一種用於 Servlet/JSP 緩存的機 制。然而,WebSphere 提供了對頁面以及片斷緩存的支持,這種支持是通過其動 態緩存功能來實現的,並且不需要對應用程序作出任何改變。其緩存的策略是聲 明性的,而且其配置是通過 XML 配置描述符來實現的。因此,您的應用程序不會 受到影響,並保持與 Java EE 規范的兼容性和移植性,同時還從 WebSphere 的 Servlet 及 JSP 的緩存機制中得到性能的優化。
從 Servet 及 JSP 的動態緩存機制得到的性能的提高是顯而易見的,這取決 於應用程序的特性。Cox 和 Martin [Cox] 指出,對一個現有的 RDF(資源描述 格式)站點摘要 (RSS) Servlet 使用動態緩存時,其性能可以提高 10%。請注意 這個實驗只涉及到一個簡單的 Servlet,這個性能的增長量可能並不能反映一個 復雜的應用程序。
為了更多地提高性能,將 WebSphere Servlet/JSP 結果緩存與 WebSphere 插 件 ESI Fragment 處理器、IBM HTTP Server Fast Response Cache Accelerator (FRCA) 和 Edge Server 緩存功能集成在一起。對於繁重的基於讀取的工作負荷 ,通過使用這些功能可以得到許多額外的好處。(請參見參考資料的 [Willenborg] 和 [Bakalova] 中描述的性能的提高。)
作為該原則的另一個示例(我們常常發現客戶不使用它,僅僅是因為他們根本 不知道它的存在),在編寫 JDBC 代碼時可以利用 WebSphere Prepared Statement Cache。在缺省情況下,當您在 WebSphere Application Server 中使 用 JDBC PreparedStatement 時,它將對該語句進行一次編譯,然後將其放到緩 存中以便再次使用,不僅可以在創建 PreparedStatement 的同一方法中重用,還 可以跨程序重用,只要其中使用了相同的 SQL 代碼或者另一個 PreparedStatement。省去重新編譯的步驟可以極大降低調用 JDBC 驅動程序的次 數,並且提高應用程序的性能。要利用這個特性,您只需編寫相應的 JDBC 代碼 以使用 PreparedStatements,而不需要進行任何其他工作。在編寫代碼時,使用 PreparedStatement 代替常規的 JDBC Statement 類(它使用了純的動態 SQL) ,您就可以實現性能的增強,而不會損失任何可移植性。
13. 充分利用現有的環境。
提供一個 Java EE EAR 和可配置的安裝腳本,而不是黑盒二進制安裝程序。
在大多數的實際場景中,大量的 WebSphere Application Server 用戶在相同 的共享單元中運行多個應用程序。這意味著,如果您提供一個需要安裝的應用程 序,那麼它必須能夠合理地安裝到現有的基礎設施中。這意味兩個方面:首先, 您必須限制關於環境的假設的數目,並且因為您無法預料到所有的情況,所以您 的安裝過程必須是可見的。這裡所說的可見是指,不應該提供二進制可執行文件 形式的安裝程序。執行安裝任務的管理員需要清楚安裝過程對他們的單元所進行 的操作。為了實現這種方式,您應該提供一個 EAR 文件(或者一組 EAR 文件) 以及相關的文檔和安裝腳本。這些腳本應該具有可讀性,以便安裝程序能夠知道 它們需要執行的操作,並對腳本的內容進行驗證以確保不會執行任何危險的操作 。在有些情況下,腳本並不合適,用戶可能需要使用一些曾用過的其他方法來安 裝 EAR,這表示您必須記錄安裝程序所完成的工作!
14. 充分利用應用服務器環境所提供的服務質量。
設計可使用 WebSphere Application Server Network Deployment 集群的應 用程序。
我們已經介紹了利用 WebSphere Application Server 安全和事務支持的重要 性。還有一個更重要的、常常被我們忽視的問題,即集群。需要將應用程序設計 為能夠運行於集群的環境。大多數實際的環境需要通過集群來實現可擴展性和可 靠性。無法進行集群的應用程序很快會導致災難的出現。
與集群緊密相關的是支持 WebSphere Application Server Network Deployment。如果您正在構建一個應用程序並打算將它賣給其他人,請確保您的 應用程序可以運行於 WebSphere Application Server Network Deployment,而 不僅僅是單個服務器版本。
15. 利用 Java EE,不要欺騙。
致力於構建真正利用 Java EE 功能的 Java EE 應用程序。
有件非常煩人的事情我們曾多次遇到過,某個應用程序聲稱可以運行於 WebSphere 中,但它並不是一個真正的 WebSphere 應用程序。我們曾見過幾個這 樣的示例,其中有一小段代碼(可能是一個 Servlet)位於 WebSphere Application Server 中,而其余所有的應用程序邏輯實際上位於單獨的進程中, 例如一個以 Java、C、C++ 或其他語言(沒有使用 Java EE)編寫的守護進程負 責完成實際的工作。這並不是一個真正的 WebSphere Application Server 應用 程序。對於這樣的應用程序,WebSphere Application Server 所提供的幾乎所有 的服務質量都不可用。對於那些認為這是 WebSphere Application Server 應用 程序的人來說,他們會突然的醒悟過來,原來並非如此。
16. 安排進行版本更新。
更改是在所難免的。安排新的發行版和修復程序更新,以便您的客戶能夠獲得 最新的版本。
WebSphere Application Server 在不斷地發展,所以 IBM 定期地給出 WebSphere Application Server 的修復程序,這是很正常的,並且 IBM 還定期 地發布新的主要版本。您需要為此做好安排。這會影響到兩類開發組織:內部開 發人員和第三方應用程序供應商。基本的問題是相同的,但對兩者的影響則有所 不同。
首先考慮修復程序。IBM 定期發布建議更新,以修復產品中已發現的錯誤。盡 管不太可能始終運行於最新的級別,但請注意,不要隔得太久。那麼究竟“隔多 久”是可以接受的呢?對於這個問題沒有什麼正確的答案,但是您應該安排好對 幾個月內的發行版進行修復級別更新。是的,這表示一年要更新好幾次。內部開 發人員可以忽略某些修復級別,一次僅支持一個修復級別,以降低測試成本。應 用程序供應商則沒有這麼幸運。如果您是應用程序供應商,那麼您同時需要支持 多種修復級別,以便您的客戶能夠將您的軟件與其他軟件一同運行。如果您僅支 持一種修復級別,那麼很可能無法找到同時兼容於多種產品的修復級別。實際上 對於供應商而言,最好的方法是使用支持“向上兼容修復程序”的模型。IBM 使 用了這種方法來支持所集成的來自其他供應商的產品(如 Oracle®、 Solaris™ 等等)。有關更詳細的信息,請參考我們的支持策略。
下面再考慮一下主要版本更新。IBM 定期地發布新的主要發行版,其中對我們 的產品進行了主要的功能更新。我們暫時繼續支持舊的主要發行版,但不會太久 。這意味著您必須安排從一個主要發行版轉到另一個主要發行版。這是不可避免 的,並且應該在您的成本模型中加以考慮。如果您是供應商,這意味著您必須經 常地對您的產品進行更新,以支持新的 WebSphere Application Server 版本, 否則您的客戶將停滯於不受支持的 IBM 產品,我們曾多次碰到過這種情況!如果 您正從供應商處購買產品,我們鼓勵您要留心您的供應商,以確保他們承諾支持 IBM 產品新的版本。停滯於不受支持的軟件是一種非常危險的情況。
17. 在代碼中所有關鍵的地方,使用標准的日志框架記錄程序的狀態。
這包括異常處理程序。使用像 JDK 1.4 Logging 或 Log4J 這樣的日志框架。
有些時候,日志記錄是最乏味的工作,降低了編程的價值,但是這樣做可以減 少調試的時間,並盡快地完成相應的任務。根據一般的經驗,在每個過渡的地方 ,需要進行日志記錄。當您將參數從一個方法傳遞到另一個方法,或從一個類傳 遞到另一個類,需要進行日志記錄。在對一個對象進行某種轉換時,需要進行日 志記錄。在碰到不解之處時,需要進行日志記錄。
在決定了進行日志記錄之後,需要選擇一種合適的框架。實際上有許多選擇, 但是我們偏愛 JDK 1.4 Trace API,因為它們已全面地集成到了 WebSphere Application Server 跟蹤子系統中,並且是基於標准的。
18. 在完成相應的任務後,請始終進行清理。
如果您從池中獲取了一個對象,請始終確保將其返回到池中。
無論運行於開發、測試或生產環境中,我們發現 Java EE 應用程序最常見的 錯誤之一是內存洩漏。絕大部分情況是因為開發人員忘了關閉連接(大多數情況 下是 JDBC 連接)或將對象返回到池中。對於任何需要顯式關閉的或需要返回到 池中的對象,請確保進行了這樣的操作。不要編寫出這樣糟糕的代碼。
19. 在開發和測試過程中遵循嚴格的程序。
這包括采用和遵循軟件開發方法學。
大型系統的開發是非常困難的,所以應該十分謹慎。但是,我們常常發現一些 團隊疏於管理、或者不能全心全意地遵循相關的開發方法(這些方法可能不適用 於他們正在進行的開發類型)、或者他們並沒有很好地理解這一點。最為糟糕的 可能是嘗試每個月更換不同的開發方法,在單個項目的生命周期中,一個團隊從 RUP 改變為 XP,以及一些其他敏捷方法。
總之,對於大多數團隊而言,只要團隊成員能夠很好地理解、嚴格地執行、並 根據特定的技術本質和使用該方法的團隊進行適當的調整,那麼幾乎任何一種方 法都是有效的。對於那些尚未采用任何方法、或者那些不能夠完全地利用所選方 法的團隊,我們建議他們參考一些優秀的著作,如 [Jacobson]、[Beck1] 或 [Cockburn]。另一個有價值的信息來源是最近公布的用於 Eclipse Process Framework [Eclipse] 的 OpenUP 插件。對於這個已經介紹過的主題,我們不想 做過多的重復,建議讀者參考 [Hambrick] 和 [Beaton2](請參見參考資料)。
結束語
在這個簡短的摘要中,我們已經向您介紹了 Java EE 中的核心模式和最佳實 踐,它們使得 Java EE 開發成為一種可管理的過程。盡管我們並沒有給出所有在 實踐中使用這些模式的必要細節,但是我們希望能夠給您足夠的指點和指導,以 幫助您決定下一步要做什麼。