周警偉
一. 設(shè)計(jì)模式重要性
采用EJB技術(shù)的J2EE項(xiàng)目中,EJB架構(gòu)的設(shè)計(jì)好壞將直接影響系統(tǒng)的性能、可擴(kuò)展性、可維護(hù)性、組件可重用性及開(kāi)發(fā)效率。項(xiàng)目越復(fù)雜,項(xiàng)目隊(duì)伍越龐大則越能體現(xiàn)良好設(shè)計(jì)的重要性。
二. 常見(jiàn)EJB設(shè)計(jì)模式
Session Facade Pattern
通常項(xiàng)目中,客戶端往往需要頻繁的對(duì)服務(wù)器端數(shù)據(jù)進(jìn)行操作。當(dāng)采用實(shí)體EJB作為數(shù)據(jù)的抽象層時(shí),如果直接讓客戶端程序與實(shí)體EJB交互,會(huì)產(chǎn)生實(shí)現(xiàn)一個(gè)業(yè)務(wù)需求便需要大量的EJB屬性操作(如下圖1)。這直接導(dǎo)致如下問(wèn)題:網(wǎng)絡(luò)負(fù)載大(遠(yuǎn)程客戶端時(shí))、并發(fā)性能低、客戶端與服務(wù)器端關(guān)聯(lián)度大、可重用性和可維護(hù)性差、性能
因此有必要在客戶端與實(shí)體EJB層間加入Session EJB層,在Sessino EJB中實(shí)現(xiàn)商業(yè)邏輯并封裝對(duì)實(shí)體EJB的操作。(如下圖2)
圖1:客戶端直接與實(shí)體EJB交互
圖2:通過(guò)SessionEJB層實(shí)現(xiàn)
Session Fa?ade模式的好處是:降低了網(wǎng)絡(luò)負(fù)載,SessionEjb可以調(diào)用實(shí)體EJB的本地接口;將商業(yè)邏輯與商業(yè)數(shù)據(jù)隔離;維護(hù)與開(kāi)發(fā)方便;顯著提高性能。
Session Fa?ade模式因其簡(jiǎn)單使用,是目前使用很廣的模式。但具體應(yīng)用過(guò)程中應(yīng)注意:避免將所有的操作封裝到一個(gè)很大的SessionEJB內(nèi);服務(wù)器端數(shù)據(jù)結(jié)構(gòu)應(yīng)由實(shí)體EJB實(shí)現(xiàn),除非特例否則避免直接的數(shù)據(jù)庫(kù)操作;SessionEjb內(nèi)某些系統(tǒng)通用操作的代碼容易重復(fù)(比如權(quán)限檢查等,解決辦法是將系統(tǒng)通用服務(wù)封裝在Java Class內(nèi))。
Message Facade Pattern
很多時(shí)候,一次Request需要操作多個(gè)EJB又不需要得到即時(shí)返回。對(duì)這種異步調(diào)用,通常應(yīng)用Message Fa?ade Pattern.
這種時(shí)候,如采用Session Fa?ade Pattern存在如下問(wèn)題:
1. 客戶端等待返回的時(shí)間過(guò)長(zhǎng)。一個(gè)SessionEjb的實(shí)例在完成客戶請(qǐng)求過(guò)程中中涉及到的每一次對(duì)其他實(shí)體Ejb的調(diào)用過(guò)程中都會(huì)被鎖定直到得到實(shí)體EJB返回信息后才能進(jìn)行下一步操作。這樣造成客戶不必要的等待,并很容易因時(shí)間導(dǎo)致整個(gè)事務(wù)失敗。
2. 系統(tǒng)可靠性和容錯(cuò)性低。如果需要調(diào)用不同系統(tǒng)或服務(wù)器上或多個(gè)異構(gòu)數(shù)據(jù)源的多個(gè)EJB時(shí),任何一個(gè)環(huán)節(jié)出錯(cuò),均導(dǎo)致客戶請(qǐng)求失敗。
以Message-Driven Bean為基礎(chǔ)的Message Facade Pattern則可以解決上述異步請(qǐng)求需求。具體架構(gòu)見(jiàn)下圖3
圖3:使用Message Facade Pattern
Message Facade Pattern的不足之處在于:
1. Message-Driven Bean沒(méi)有返回值。這樣通知客戶執(zhí)行結(jié)果只能依賴于Email或人工等其他手段。
2. Message-Driven Bean執(zhí)行過(guò)程中無(wú)法將捕獲的異常直接返回給客戶端,即無(wú)法使客戶端直接直到錯(cuò)誤信息。
3. Message-Driven Bean通過(guò)接收Message響應(yīng)客戶請(qǐng)求,對(duì)Message內(nèi)容的合法性(比如對(duì)象的類(lèi)型等)依賴與客戶端.容易產(chǎn)生運(yùn)行時(shí)錯(cuò)誤。
Message Facade Pattern經(jīng)常與Session Facade Pattern在同一個(gè)項(xiàng)目里共同使用。
EJB Command Pattern
Session Facade Pattern中將商業(yè)邏輯實(shí)現(xiàn)封裝在Session EJB中,這種做法帶來(lái)諸多益處之外也帶來(lái)如下問(wèn)題:
1. 由于業(yè)務(wù)經(jīng)常的變化,導(dǎo)致經(jīng)常需要更新Session EJB代碼。
2. 客戶端代碼不得不包含大量EJB相關(guān)的API,不利于后期項(xiàng)目維護(hù)。
3. 項(xiàng)目開(kāi)發(fā)測(cè)試需要經(jīng)常的EJB重部署過(guò)程。
引起上述問(wèn)題的重要根結(jié)就是Session EJB本身重量級(jí)組件,其開(kāi)發(fā)測(cè)試部署工作量較大,開(kāi)發(fā)周期較長(zhǎng)。以上不足可以通過(guò)EJB Command Pattern克服。
EJB Command Pattern中將商業(yè)邏輯實(shí)現(xiàn)封裝在普通的Java Class(稱之為Command Bean)中。該模式的具體實(shí)現(xiàn)有很多種,通常的框架都包括三部分:
1. Command Bean.由應(yīng)用開(kāi)發(fā)者寫(xiě)的具體實(shí)現(xiàn)某商業(yè)操作的Java Class.主要包含getXXX(),setXXX(),execute()方法。
2. Client-Side Routing Logic.由多個(gè)Class組成,用于將請(qǐng)求轉(zhuǎn)發(fā)至Command Sever,這個(gè)過(guò)程對(duì)客戶是透明的。這部分代碼可以跨項(xiàng)目使用。路由規(guī)則中可以考慮用XML技術(shù)。
3. Remote Command Server.實(shí)際執(zhí)行商業(yè)操作請(qǐng)求。通常可以用Session EJB層實(shí)現(xiàn)。
整個(gè)框架見(jiàn)下圖4:
圖4:Command的基本框架
EJB Command Pattern具有如下好處:
1. 適應(yīng)與需要快速開(kāi)發(fā)環(huán)境。因Command Bean是輕量級(jí)的Java Class,其編譯和調(diào)試比較方便。
2. 將表現(xiàn)層與商業(yè)實(shí)現(xiàn)層隔離,同時(shí)將客戶端代碼與EJB層隔離。
3. 將客戶端代碼開(kāi)發(fā)與服務(wù)器端代碼開(kāi)發(fā)相對(duì)清晰。早期可以創(chuàng)建空的Command Bean方便客戶端代碼調(diào)試。
EJB Command Pattern的弱處在于:
1. Command Bean中對(duì)事務(wù)的控制不如Session EJB中。
2. Command Bean是無(wú)狀態(tài)的。
3. 無(wú)法將異常直接返回給客戶。
4. 在大項(xiàng)目中,由于商業(yè)邏輯復(fù)雜,常導(dǎo)致大數(shù)量的Command Bean存在.
5. 作為Command Server的Session EJB打包時(shí)必須包含Command Bean以致存在維護(hù)上的不便。
EJB Command Pattern的一個(gè)實(shí)際實(shí)現(xiàn)可以參考IBM's Command Framework.
Data Transfer Object Factory
基于EJB的J2EE項(xiàng)目,經(jīng)常需要在客戶端與服務(wù)器端傳輸大量數(shù)據(jù)。數(shù)據(jù)的組織形式常用的是DTO(Data Transfer Object,服務(wù)器端數(shù)據(jù)對(duì)象的抽象)。但因?yàn)榭蛻舳吮憩F(xiàn)層經(jīng)常是變化的,所需要服務(wù)器端數(shù)據(jù)也變動(dòng)頻繁,換句話說(shuō),DTO的數(shù)量和屬性經(jīng)常要更改。因此如何以及在何處生成和維護(hù)DTO便是需要考慮的問(wèn)題。
一種解決方案是直接在Entity EJB中直接處理,即在Entity EJB的Bean類(lèi)中加入getXXXDTO()、setXXXDTO()等。但這樣做導(dǎo)致EJB與DTO層緊緊綁定。一旦DTO更改,與該DTO相關(guān)的EJB即需要重編譯打包。EJB層與客戶端層相關(guān)聯(lián)不僅使維護(hù)困難而且導(dǎo)致EJB的重用性大大降低。
更好的解決方案是利用Data Transfer Object Factory封裝對(duì)DTO的操作邏輯(如下圖6)。
圖6:DTO Factory示例
DTO Factory具體實(shí)現(xiàn)方式通常有兩種:
1. 普通Java Class實(shí)現(xiàn),用于Session Facade Pattern使用DTO環(huán)境下。
2. Stateless Session EJB實(shí)現(xiàn),用于非EJB客戶端使用DTO環(huán)境下(見(jiàn)圖7)。
圖7:SessionEJB實(shí)現(xiàn)DTOFactory
DTO Factory帶來(lái)如下好處:
1. 使Entity EJB的重用成為可能。由于不含DTO處理邏輯,Entity EJB功能單一化,只作為數(shù)據(jù)源。不通客戶端通過(guò)各自的DTO Factory可以從同一個(gè)Entity EJB得到各自所需的個(gè)性化數(shù)據(jù)(自定義DTO)。
2. 提高可維護(hù)性和性能。
3. 可以根據(jù)在DTO Factory層生成很復(fù)雜的DTO結(jié)構(gòu),諸如繼承、關(guān)聯(lián)關(guān)系等,而對(duì)客戶端提供一個(gè)透明、細(xì)化的數(shù)據(jù)接口。
使用DTO Factory時(shí)需要注意的是:不需為每個(gè)Entity EJB定義一個(gè)Factory。可以為一系列相關(guān)的Entity EJB創(chuàng)建一個(gè)Factory,或者只創(chuàng)建一個(gè)Factory。
Generic Attribute Access
使用Entity EJB作為商業(yè)數(shù)據(jù)層時(shí),我們首先需要從數(shù)據(jù)庫(kù)加載數(shù)據(jù),創(chuàng)建對(duì)應(yīng)的Entity EJB實(shí)例,之后對(duì)內(nèi)存中Entity EJB實(shí)例的屬性進(jìn)行相應(yīng)操作。對(duì)屬性的操作比較直接的做法是:直接調(diào)用Entity EJB的getXXX()/setXXX(),通常利用EJB2.0的本地接口;通過(guò)DTO Factory生成DTO。但這兩種做法都存在如下問(wèn)題:
1. 當(dāng)Entity EJB的屬性特別多時(shí)候,以上做法會(huì)帶來(lái)復(fù)雜羅嗦的代碼,使EJB變的龐大無(wú)比。
2. 使Entity EJB的客戶端(比如Session EJB)和Entity EJB的接口緊密關(guān)聯(lián)。Entity EJB屬性的增刪都需要更改客戶端代碼,給項(xiàng)目開(kāi)發(fā)和維護(hù)帶來(lái)不便。
事實(shí)上可以利用更通用的方式訪問(wèn)Entity EJB的屬性,即定義Generic Attribute Access Interface。見(jiàn)下圖8:
圖8:Generic Attribute Access Interface示例
Generic Attribute Access Interface由Entity EJB的本地或遠(yuǎn)程接口實(shí)現(xiàn),并利用Hash Maps傳輸數(shù)據(jù)。實(shí)現(xiàn)方式常見(jiàn)如下:
1. BMP類(lèi)型實(shí)體EJB可以在Bean類(lèi)中定義包含所有屬性的私有成員變量HashMap。
2. CMP類(lèi)型實(shí)體EJB可以在Bean類(lèi)中可以適用Java Reflection API實(shí)現(xiàn)。
3. 建立一個(gè)父類(lèi),在不同的情況下定義子類(lèi)重載父類(lèi)方法。
使用Generic Attribute Access Interface需要在客戶端與服務(wù)器端對(duì)屬性以及對(duì)應(yīng)的關(guān)鍵字建立統(tǒng)一的命名習(xí)慣。常見(jiàn)的做法如下:
1. 建立并保持良好的文檔記錄和命名約定。
2. 在實(shí)體EJB的實(shí)現(xiàn)類(lèi)中定義靜態(tài)成員映射屬性。
3. 創(chuàng)建共享靜態(tài)類(lèi),通過(guò)成員變量映射實(shí)體EJB屬性。
4. 通過(guò)JNDI在服務(wù)器端保存屬性映射關(guān)系。
Generic Attribute Access Interface的運(yùn)用帶來(lái)一下益處:
1. 接口實(shí)現(xiàn)后對(duì)不通實(shí)體EJB都適用。
2. 對(duì)屬性較多實(shí)體EJB能精簡(jiǎn)代碼,并更具維護(hù)性。
3. 使運(yùn)行中動(dòng)態(tài)增刪實(shí)體EJB屬性成為可能。
Generic Attribute Access Interface的缺點(diǎn)在于:
1. 訪問(wèn)EJB屬性時(shí)增加了額外的操作。需要通過(guò)關(guān)鍵字映射屬性,最后還需進(jìn)行類(lèi)型轉(zhuǎn)換。
2. 需要建立客戶端與服務(wù)器端的命名約定。
3. 因?yàn)橥ㄟ^(guò)HashMap操作時(shí)候需要進(jìn)行類(lèi)型轉(zhuǎn)換,容易產(chǎn)生運(yùn)行時(shí)類(lèi)型不匹配異常。
Business Interface
EJB規(guī)范要求Bean實(shí)現(xiàn)類(lèi)必須實(shí)現(xiàn)所有在遠(yuǎn)程(或本地)接口中定義的所有方法,同時(shí)不允許Bean實(shí)現(xiàn)類(lèi)直接繼承遠(yuǎn)程(或本地)接口。這就導(dǎo)致編譯時(shí)候很容易產(chǎn)生兩者不一致的問(wèn)題,即遠(yuǎn)程(或本地)接口中定義的某方法為在Bean實(shí)現(xiàn)類(lèi)中被實(shí)現(xiàn)等錯(cuò)誤。為避免上訴錯(cuò)誤,可以利用應(yīng)用服務(wù)器廠商所提供的工具。但也可以應(yīng)用EJB的設(shè)計(jì)架構(gòu)來(lái)實(shí)現(xiàn):定義商業(yè)接口。
Business Interface即創(chuàng)建自定義商業(yè)接口,在接口中定義所有EJB提供的商業(yè)方法,并讓Bean實(shí)現(xiàn)類(lèi)和遠(yuǎn)程(或本地)接口都實(shí)現(xiàn)該商業(yè)接口。其繼承關(guān)系見(jiàn)下圖9:
圖9:商業(yè)接口的使用
Business Interface是個(gè)普通的Java Class。依賴于使用本地接口與遠(yuǎn)程接口的不通,Business Interface的定義略有不同:應(yīng)用與遠(yuǎn)程接口時(shí),在接口中的方法需要拋出java.rmi.RemoteException;而應(yīng)用與本地接口時(shí)候則不需要作任何特別處理。
應(yīng)用Business Interface時(shí)候必須注意一點(diǎn):EJB規(guī)范不允許直接EJB的實(shí)例將對(duì)自己的引用(this對(duì)象)返回給客戶端,否則編譯時(shí)候即報(bào)錯(cuò)。但使用Business Interface后,編譯時(shí)候無(wú)法檢查出有無(wú)將this對(duì)象返回給客戶端。這一點(diǎn)需要程序員自己保證。
三. 內(nèi)部數(shù)據(jù)轉(zhuǎn)換策略
Data Transfer Object
基于EJB的J2EE多層架構(gòu)應(yīng)用中,經(jīng)常涉及的一個(gè)問(wèn)題就是如何在各層之間傳遞批量數(shù)據(jù),比如客戶端對(duì)服務(wù)器端數(shù)據(jù)的批量讀寫(xiě)操作等。比如需要得到實(shí)體EJB的屬性,直接的方法是多次調(diào)用不通的屬性,如下圖10:
圖10:低效的數(shù)據(jù)傳遞方式
但這種方法容易導(dǎo)致許多問(wèn)題,比如性能以及代碼的復(fù)雜度等,更有效的辦法是在一個(gè)調(diào)用中得到所有需要的屬性。所以可以引入Data Transfer Object來(lái)封裝所需要的屬性,并在客戶與服務(wù)器端通過(guò)傳遞該對(duì)象一次實(shí)現(xiàn)對(duì)數(shù)據(jù)的操作。如下圖11:
圖11:通過(guò)DTO傳遞數(shù)據(jù)
DTO為普通的Java Class,通常是服務(wù)器端數(shù)據(jù)的快照。由于網(wǎng)絡(luò)傳輸?shù)男枰珼TO應(yīng)該實(shí)現(xiàn)java.io.Serializable接口。
DTO的設(shè)計(jì)有兩種模型:Domain DTO以及Custom DTO。
Domain DTO僅僅實(shí)現(xiàn)對(duì)服務(wù)器數(shù)據(jù)的拷貝,通常與實(shí)體EJB為一對(duì)一的關(guān)系(也存在為多個(gè)相關(guān)聯(lián)的實(shí)體EJB對(duì)應(yīng)一個(gè)Domain DTO)。Domain DTO通常除用于讀取更改實(shí)體EJB屬性外也可用于創(chuàng)建實(shí)體EJB時(shí)候。實(shí)體EJB與Domain DTO對(duì)應(yīng)關(guān)系如下圖12:
圖12:Account EJB 與 Account DomainDTO
Domain DTO的應(yīng)用除了DTO所具有的一般優(yōu)點(diǎn)外,還有別的益處:
1. 開(kāi)發(fā)迅速。因?yàn)橐坏?shí)體EJB設(shè)計(jì)好后,很容易轉(zhuǎn)換得到Domain DTO。
2. 可以利用Domain DTO的setXXX()方法在客戶端進(jìn)行屬性有效性效驗(yàn)。
Domain DTO的缺點(diǎn)有:
1. 客戶端綁定了服務(wù)器端數(shù)據(jù)模型,不利于維護(hù)。
2. 不夠靈活,無(wú)法處理客戶端的多樣化數(shù)據(jù)要求。對(duì)一個(gè)數(shù)百個(gè)屬性的實(shí)體EJB請(qǐng)求一個(gè)屬性時(shí)候卻返回一個(gè)包含所有屬性值的Domain DTO明顯是笨重的實(shí)現(xiàn)。
3. 導(dǎo)致代碼的重復(fù)。
4. Domain DTO中如果嵌套包含了別的Domain DTO時(shí),一旦需服務(wù)器端數(shù)據(jù)的更改而需要重定義Domain DTO模型時(shí)候異常困難。
Custom DTO則可以克服上述的一些缺點(diǎn)。Customer DTO僅僅封裝用戶感興趣的服務(wù)器數(shù)據(jù)集即可以根據(jù)客戶端需求創(chuàng)建Customer DTO。這樣作的優(yōu)點(diǎn)是靈活高效;缺點(diǎn)是大項(xiàng)目中可能導(dǎo)致大量的Customer DTO存在。
通常Domain DTO可以用于數(shù)據(jù)的更新與創(chuàng)建;Customer DTO可以用于客戶用于表現(xiàn)層的數(shù)據(jù)讀取。兩者可以相輔相成。而且使用DTO一般與DTO Factory同時(shí)使用。
Domain Transfer Hash Map
DTO的使用往往缺乏通用性。不通的用戶案例需要?jiǎng)?chuàng)建不同的DTO。當(dāng)項(xiàng)目很復(fù)雜時(shí),從維護(hù)性考慮需要更好的數(shù)據(jù)傳輸?shù)膶?shí)現(xiàn)方式。
Domain Transfer Hash Map即利用HashMap作為客戶所需數(shù)據(jù)集的封裝。好處是:
1. 良好的維護(hù)性。
2. 較大的通用性。不同的客戶端可以使用相同的數(shù)據(jù)傳遞方式。
缺點(diǎn)是:
1. 需要維護(hù)客戶端與服務(wù)器端在屬性及其對(duì)應(yīng)關(guān)鍵字的映射關(guān)系。
2. 當(dāng)需要使用基本類(lèi)型的數(shù)據(jù)時(shí)候,因?yàn)镠ash Map的限制必須將基本類(lèi)型先轉(zhuǎn)換成對(duì)象。
3. 使用得到的數(shù)據(jù)時(shí),需要進(jìn)行類(lèi)型強(qiáng)制轉(zhuǎn)換。
Data Transfer RowSet
當(dāng)需要處理直接的JDBC調(diào)用得到的結(jié)果集時(shí),顯然用DTO/Hash Map已經(jīng)不合適,因?yàn)樾枰獙?duì)大量數(shù)據(jù)進(jìn)行類(lèi)型轉(zhuǎn)換等額外操作是很費(fèi)資源和不必要的,而且最終用戶常需要以表格式樣顯示數(shù)據(jù)。
所以對(duì)二維表式數(shù)據(jù),更好的處理方式是利用Data Transfer RowSet。Data Transfer RowSet通過(guò)將ResultSet直接轉(zhuǎn)換為RowSet傳遞給客戶端。
在Session EJB中使用RowSet的一段示例代碼如下圖13:
圖13:使用RowSet
使用RowSet的好處很多:
1. 接口通用于各樣的數(shù)據(jù)庫(kù)查詢操作。
2. 當(dāng)需要表格式數(shù)據(jù)顯示時(shí),因?yàn)橹苯訌腞esultSet得到,所以不需要額外的數(shù)據(jù)類(lèi)型轉(zhuǎn)換。
缺點(diǎn)是:
1. 數(shù)據(jù)庫(kù)結(jié)構(gòu)暴露給客戶端。
2. 不符合面向?qū)ο笤O(shè)計(jì)思想。
3. 依賴于SQL。
Data Transfer RowSet通常用于只讀式數(shù)據(jù)的顯示操作,經(jīng)常和JDBC for Reading Pattern連用。
四.事務(wù)和數(shù)據(jù)持久機(jī)制
JDBC for Reading Pattern
基于EJB的J2EE應(yīng)用中,通過(guò)EJB對(duì)數(shù)據(jù)庫(kù)的操作可以有兩種方式:實(shí)體EJB或者Session EJB中直接利用JDBC訪問(wèn)。
客戶很多時(shí)候取出數(shù)據(jù)庫(kù)中數(shù)據(jù)并以表格方式顯示。這種情形如果使用實(shí)體EJB會(huì)導(dǎo)致如下問(wèn)題:
1. 引用服務(wù)器端頻繁的數(shù)據(jù)庫(kù)查詢和加載操作。因?yàn)榧虞dN個(gè)實(shí)體EJB總需要進(jìn)行一次find()操作 N次數(shù)據(jù)加載。
2. 如果使用Remote接口,引起頻繁的額外網(wǎng)絡(luò)操作。
3. 對(duì)關(guān)聯(lián)關(guān)系比較復(fù)雜的數(shù)據(jù)庫(kù)表結(jié)構(gòu),很難直接通過(guò)Entity EJB表現(xiàn)。
因此建議在只需對(duì)數(shù)據(jù)庫(kù)表數(shù)據(jù)進(jìn)行只讀訪問(wèn)時(shí)候,應(yīng)該采用JDBC for Reading Pattern,即通過(guò)JDBC直接訪問(wèn)數(shù)據(jù)庫(kù)。除了避免上述使用實(shí)體EJB的缺點(diǎn)還帶來(lái)一下好處:
1. 充分利用數(shù)據(jù)庫(kù)能力,比如數(shù)據(jù)庫(kù)的緩存機(jī)制。
2. 減少了對(duì)事務(wù)控制的資源。
3. 利用自定義SQL可以按需要比較靈活的讀取數(shù)據(jù)。
4. 只需要一次數(shù)據(jù)查詢,減少了數(shù)據(jù)庫(kù)操作。
缺點(diǎn)是:
1. 于J2EE應(yīng)用的面向?qū)ο笤O(shè)計(jì)相違背。
2. 因?yàn)镾ession EJB代碼中包含了自定義SQL,維護(hù)性差。
3. Session EJB中不得不包含JDBC的API,并且需要了解數(shù)據(jù)庫(kù)結(jié)構(gòu)。
posted @ 2005-01-29 08:48 damn Java 閱讀(641) | 評(píng)論 (0) | 編輯 收藏