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