雖然許多文章曾經討論過J2EE最佳實踐。那麼,為什麼我還要再寫一篇文章呢?本文究
竟與以前的文章有何不同或者說比其他文章好在哪呢?
首先,本文的目標讀者是正在從事技術工作的架構師。為了避免浪費大家的才智,我會避
免講述一些陳腐的最佳實踐,例如“日常構建(build daily)”、“測試一切(test everything)
”和“經常整合( integrate often)。 任何具有稱職架構師的專案都有分工明確的、定義良好
的團隊結構。他們還為進行編碼檢查、構建程式碼(每日或在需要時)、進行測試(單元、整合
和系統的)、部署和配置/釋放管理而具備已記錄的過程。
其次,我將跳過通常吹捧的最佳實踐,例如“基於介面的設計”、“使用著名的設計模型”以
及“使用面向服務的架構”等。相反,我將集中講述我曾學過並且使用了若干年的6(不是很
多)個方面的in-the-trench課程。最後,本文的目的是讓您思考一下自己的架構,提供工作
程式碼示例或者解決方案超出了本文的範圍。下面就讓我介紹一下這6課:
第1課:切勿繞過伺服器端驗證
作為一位軟體顧問,我曾有機會不但設計並實現了Web應用程式,而且還評估/稽核了許
多Web應用程式。在複雜的、並且用JavaScript客戶端封裝的應用程式內,我經常遇到對使用者
輸入資訊執行大量檢查的Web頁面。即使HTML元素具有資料有效性的屬性也如此,例如
MAXLENGTH。只有在成功驗證所有輸入資訊後,才能提交HTML表單。結果,一旦伺服器端收
到通知表單(請求),便恰當地執行業務邏輯。
在此,您發現問題了麼?開發人員已經做了許多重要的假設。例如,他們假設所有的Web
應用程式使用者都同樣誠實。開發人員還假設所有使用者將總是使用他們測試過的瀏覽器訪問Web
應用程式。還有很多其他的假設。這些開發人員忘記了利用可以免費得到的工具,通過命令列
很容易地模擬類似瀏覽器的行為。事實上,通過在瀏覽器視窗中鍵入適當的URL,您可以傳送
任何“posted”表單,儘管如此,通過禁用這些頁面的GET請求,您很容易地阻止這樣的“表單
傳送”。但是,您不能阻止人們模擬甚至建立他們自己的瀏覽器來入侵您的系統。
根本的問題在於開發人員不能確定客戶端驗證與伺服器端驗證的主要差別。兩者的主要差別不
在於驗證究竟發生在哪裡,例如在客戶端或者在伺服器端。主要的差別在於驗證背後的目的不
同。
客戶端驗證僅僅是方便。執行它可為使用者提供快速反饋??使應用程式似乎做出響應,給人
一種執行桌面應用程式的錯覺。
另一方面,伺服器端驗證是構建安全Web應用程式必需的。不管在客戶端一側輸入的是什
麼,它可以確保客戶端送往伺服器的所有資料都是有效的。
因而,只有伺服器端驗證才可以提供真正應用程式級的安全。許多開發人員陷入了錯誤感
覺的圈套:只有在客戶端進行所有資料的驗證才能確保安全。下面是說明此觀點的一個常見的
示例:
一個典型的登入頁面擁有一個用來輸入使用者名稱的文字框和一個輸入密碼的文字框。在服務
器端,某人在接收servlet中可能遇到一些程式碼,這些程式碼構成了下面形式的SQL查詢:
"SELECT * FROM SecurityTable WHERE username = '" + form.getParameter
("username") + "' AND password = '" + form.getParameter("password") + "';",並執行這
些程式碼。如果查詢在結果集的某一行返回,則使用者登入成功,否則使用者登入失敗。
第一個問題是構造SQL的方式,但現在讓我們暫時忽略它。如果使用者在使用者名稱中輸
入“Alice'--”會怎樣呢?假設名為“Alice”的使用者已經在SecurityTable中,這時此使用者(更恰當
的說法是黑客)成功地登入。我將把找出為什麼會出現這種情況的原因做為留給您的一道習
題。
許多創造性的客戶端驗證可以阻止一般的使用者從瀏覽器中這樣登入。但對於已經禁用了
JavaScript的客戶端,或者那些能夠使用其他類似瀏覽器程式直接傳送命令(HTTP POST和
GET命令)的高階使用者(或者說黑客)來說,我們又有什麼辦法呢?伺服器端驗證是防止這種
漏洞型別所必須的。這時,SSL、防火牆等都派不上用場了。
第2課:安全並非是附加物
如第1課所述,我曾有幸研究過許多Web應用程式。我發現所有的JavaServer Page
(JSP)都有一個共同的主題,那就是具有類似下面虛擬碼的佈局:
< %
User user =
session.getAttribute("User");
if(user == null)
{
// redirect to
// the logon page…
}
if(!user.role.equals("manager"))
{
// redirect to the
// "unauthorized" page…
}
%>
< !-
HTML, JavaScript, and JSP
code to display data and
allow user interaction -->
如果專案使用諸如Struts這樣的MVC框架,所有的Action Bean都會具有類似的程式碼。盡
管最後這些程式碼可能執行得很好,但如果您發現一個bug,或者您必須新增一個新的角色(例
如,“guest”或者“admin”),這就會代表一場維護惡夢。
此外,所有的開發人員,不管您多年輕,都需要熟悉這種編碼模式。當然,您可以用一些
JSP標籤來整理JSP程式碼,可以建立一個清除派生Action Bean的基本Action Bean。儘管如
此,由於與安全相關的程式碼會分佈到多個地方,所以維護時的惡夢仍舊存在。由於Web應用程
序的安全是強迫建立在應用程式程式碼的級別上(由多個開發人員),而不是建立在架構級別
上,所以Web應用程式還是很可能存在弱點。
很可能,根本的問題是在專案接近完成時才處理安全性問題。最近作為一名架構師,我曾
在一年多的時間裡親歷了某一要實現專案的6個版本,而直到第四版時我們才提到了安全性??
即使該專案會將高度敏感的個人資料暴露於Web上,我們也沒有注意到安全性。為了更改釋出
計劃,我們捲入了與專案資助人及其管理人員的爭鬥中,以便在第一版中包含所有與安全相關
的功能,並將一些“業務”功能放在後續的版本中。最終,我們贏得了勝利。而且由於應用程式
的安全性相當高,能夠保護客戶的私有資料,這一點我們引以為榮,我們的客戶也非常高
興。
遺憾的是,在大多數應用程式中,安全性看起來並未增加任何實際的商業價值,所以直到
最後才解決。發生這種情況時,人們才匆忙開發與安全相關的程式碼,而絲毫沒有考慮解決方案
的長期可維護性或者健壯性。忽視該安全性的另一個徵兆是缺乏全面的伺服器端驗證,如我在
第1課中所述,這一點是安全Web應用程式的一個重要組成部分。
記住:J2EE Web應用程式的安全性並非僅僅是在Web.xml 和ejb-jar.xml檔案中使用合適
的宣告,也不是使用J2EE技術,如Java 認證和授權服務(Java Authentication and
Authorization Service,JAAS)。而是經過深思熟慮後的設計,且實現一個支援它的架構。
第3課:國際化(I18N)不再是紙上談兵
當今世界的事實是許多英語非母語的人們將訪問您的公共Web應用程式。隨著電子政務的
實行,由於它允許人們(某個國家的居民)線上與政府機構互動,所以這一點特別真實。這樣
的例子包括換髮駕照或者車輛登記證。許多第一語言不是英語的人們很可能將訪問這樣的應用
程式。國際化(即:“i18n”,因為在“internationalization”這個單詞中,字母i和字母n之間一
共有18個字母)使得您的應用程式能夠支援多種語言。
顯然,如果您的JSP 頁面中有硬編碼的文字,或者您的Java程式碼返回硬編碼的錯誤訊息,
那麼您要花費很多時間開發此Web應用程式的西班牙語版本。然而,在Web應用程式中,為了
支援多種語言,文字不是惟一必須“具體化”的部分。因為許多影像中嵌有文字,所以圖形和圖
像也應該是可配置的。在極端的情況下,影像(或者顏色)在不同的文化背景中可能有完全不
同的意思。類似地,任何格式化數字和日期的Java程式碼也必須本地化。但問題是:您的頁面布
局可能也需要更改。
例如,如果您使用HTML表格來格式化和顯示選單選項、應用程式題頭或註腳,則您可能
必須為每一種支援的語言更改每一欄的最小寬度和表格其他可能的方面。為了適應不同的字型
和顏色,您可能必須為每一種語言使用單獨的樣式表。
顯然,現在建立一個國際化的Web應用程式面臨的是架構挑戰而不是應用程式方面的挑
戰。一個架構良好的Web應用程式意味著您的JSP頁面和所有與業務相關的(應用程式特有
的)Java程式碼都不知不覺地選擇了本地化。要記住的教訓是:不要因為Java、J2EE支援國際
化而不考慮國際化。您必須從第一天起就記住設計具有國際化的解決方案。
第4課:在MVC表示中避免共同的錯誤
J2EE開發已經足夠成熟,在表示層,大多數專案使用MVC架構的某些形式,例如Struts。
在這樣的專案中,我常見到的現象是對MVC模式的誤用。下面是幾個示例。
常見的誤用是在模型層(例如,在Struts的Action Bean中)實現了所有的業務邏輯。不
要忘了,表示層的模型層仍然是表示層的一部分。使用該模型層的正確方法是呼叫適當的業務
層服務(或物件)並將結果傳送到檢視層(view layer)。用設計模式術語來說,MVC表示層
的模型應該作為業務層的外觀(Fa?ade)來實現。更好的方法是,使用核心J2EE模式(Core
J2EE Patterns)中論述到的Business Delegate模式。這段自書中摘錄的內容精彩地概述了
將您的模型作為Business Delegate來實現的要點和優點:
Business Delegate起到客戶端業務抽象化的作用。它抽象化,進而隱藏業務服務的實
現。使用Business Delegate,可以降低表示層客戶端和系統的業務服務.之間的耦合程度。根
據實現策略不同,Business Delegate可以在業務服務API的實現中,保護客戶端不受可能的變
動性影響。這樣,在業務服務API或其底層實現變化時,可以潛在地減少必須修改表示層客戶
端程式碼的次數。
另一個常見的錯誤是在模型層中放置許多表示型別的邏輯。例如,如果JSP頁面需要以指
定方式格式化的日期或者以指定方式排序的資料,某些人可能將該邏輯放置在模型層,對該邏
輯來說,這是錯誤的地方。實際上,它應該在JSP頁面使用的一組helper類中。當業務層返回
資料時,Action Bean應該將資料轉發給檢視層。這樣,無需建立模型和檢視之間多餘的耦
合,就能夠靈活支援多個檢視層(JSP、Velocity、XML等)。也使檢視能夠確定向使用者顯示
資料的最佳方式。
最後,我見過的大多數MVC應用程式都有未充分應用的控制器。例如,絕大多數的Struts
應用程式將建立一個基本的Action類,並完成所有與安全相關的功能。其他所有的Action
Bean都是此基類的派生類。這種功能應該是控制器的一部分,因為如果沒有滿足安全條件,則
首先呼叫不應該到達Action Bean(即:模型)。記住,一個設計良好的MVC架構的最強大功
能之一是存在一個健壯的、可擴充套件的控制器。您應該利用該能力以加強自己的優勢。
第5課:不要被JOPO束縛住手腳
我曾目睹許多專案為了使用Enterprise JavaBean而使用Enterprise JavaBean。因為EJB
似乎給專案帶來優越感和妄自尊大的表現,所以有時它是顯酷的要素(coolness factor)。而
其他時候,它會使J2EE和EJB引起混淆。記住,J2EE和EJB不是同意詞。EJB只是J2EE 的一部
分,J2EE 是包含JSP、servlet、Java 訊息服務(JMS)、Java資料庫連線(JDBC)、
JAAS、 Java管理擴充套件(JMX)和EJB在內的一系列技術,同樣也是有關如何共同使用這些技
術建立解決方案的一組指導原則和模式。
如果在不需要使用EJB的情況下使用EJB,它們可能會影響程式的效能。與老的Web伺服器
相比,EJB一般對應用伺服器有更多的需求。EJB提供的所有增值服務一般需要消耗更大的記憶體
和更多的CPU時間。許多應用程式不需要這些服務,因此應用伺服器要與應用程式爭奪資
源。
在某些情況下,不必要地使用EJB可能使應用程式崩潰。例如,最近我遇到了一個在開源
應用伺服器上開發的應用程式。業務邏輯封裝在一系列有狀態會話bean(EJB)中。開發人員
為了在應用伺服器中完全禁用這些bean的“鈍化”費了很大的勁。客戶端要求應用程式部署在某
一商用應用伺服器上,而該伺服器是客戶端技術棧的一部分。該應用伺服器卻不允許關閉“鈍
化”功能。事實上,客戶端不想改變與其合作的應用伺服器的設任何置。結果,開發商碰到了
很大的麻煩。(似乎)有趣的事情是開發商自己都不能給出為什麼將程式碼用EJB(而且還是有
狀態會話bean)實現的好理由。不僅僅是開發商會遇到效能問題,他們的程式在客戶那裡也無法工作。
在Web應用程式中,無格式普通Java 物件(POJO)是EJB強有力的競爭者。POJO是輕量
級的,不像EJB那樣負擔額外的負擔。在我看來,對許多EJB的優點,例如物件入池,估計過
高。POJO是您的朋友,不要被它束縛住手腳。
第6課:資料訪問並不能託管O/R對映
我曾參與過的所有Web應用程式都向使用者提供從其他地方存取的資料,並且因此需要一個
資料訪問層。這並不是說所有的專案都需要標識並建立這樣一個層,這僅僅說明這樣層的存在
不是隱含的就是明確的。如果是隱含的資料層,資料層是業務物件(即:業務服務)層的一部
分。這適用於小型應用程式,但通常與大一些專案所接受的架構指導原則相牴觸。
總之,資料訪問層必須滿足或超出以下四個標準:
具有透明性
業務物件在不知道資料來源實現的具體細節情況下,可以使用資料來源。由於實現細節隱藏在
資料訪問層的內部,所以訪問是透明的。
易於遷移
資料訪問層使應用程式很容易遷移到其他資料庫實現。業務物件不瞭解底層的資料實現,
所以遷移僅僅涉及到修改資料訪問層。進一步地說,如果您正在部署某種工廠策略,您可以為
每個底層的儲存實現提供具體的工廠實現。如果是那樣的話,遷移到不同的儲存實現意味著為
應用程式提供一個新的工廠實現。
儘量減少業務物件中程式碼複雜性
因為資料訪問層管理著所有的資料訪問複雜性,所以它可以簡化業務物件和使用資料訪問
層的其他資料客戶端的程式碼。資料訪問層,而不是業務物件,含有許多與實現相關的程式碼(例
如SQL語句)。這樣給開發人員帶來了更高的效率、更好的可維護性、提高了程式碼的可讀性等
一系列好處。
把所有的資料訪問集中在單獨的層上
由於所有的資料訪問操作現在都委託給資料訪問層,所以您可以將這個單獨的資料訪問層
看做能夠將應用程式的其他部分與資料訪問實現相互隔離的層。這種集中化可以使應用程式易
於維護和管理。
注意:這些標準都不能明確地調出對O/R(物件到關係)對映層的需求。O/R對映層一般
用O/R對映工具建立,它提供物件對關係資料結構的檢視和感知(look-and-feel)。在我看
來,在專案中使用O/R對映與使用EJB類似。在大多數情況下,並不要求它。對於包含中等規模
的聯合以及多對多關係的關係型資料庫來說,O/R對映會變得相當複雜。由於增加O/R 對映解
決方案本身的內在複雜性,例如延遲載入(lazy loading)、高速緩衝等,您將為您的專案帶
來更大的複雜性(和風險)。
為了進一步支援我的觀點,我將指出按照Sun Microsystem所普及的實體Bean(O/R對映
的一種實現)的許多失敗的嘗試,這是自1.0版以來一直折磨人的難題。在SUN的防衛措施
中,一些早期的問題是有關EJB規範的開發商實現的。這依次證明了實體Bean規範自身的複雜
性。結果,大多數J2EE架構師一般認為從實體Bean中脫離出來是一個好主意。
大多數應用程式在處理他們的資料時,只能進行有限次數的查詢。在這樣的應用程式中,
訪問資料的一種有效方法是實現一個資料訪問層,該層實現執行這些查詢的一系列服務(或對
象、或API)。如上所述,在這種情況下,不需要O/R對映。當您要求查詢靈活性時,O/R對映
正合適,但要記住:這種附加的靈活性並不是沒有代價的。
就像我承諾的那樣,在本文中,我儘量避免陳腐的最佳實踐。相反,關於J2EE專案中每一
位架構師必須做出的最重要的決定,我集中講解了我的觀點。最後,您應該記住:J2EE並非某
種具體的技術,也不是強行加入到解決方案中的一些首字母縮寫。相反,您應該在適當的時
機,恰當的地方,使用合適的技術,並遵循J2EE的指導原則和J2EE中所包含的比技術本身重要
得多的實踐。