摘要: 
  這篇文章將討論怎樣組合幾個(gè)著名的框架去做到松耦合的目的,怎樣建立你的構(gòu)架,怎樣讓你的各個(gè)應(yīng)用層保持一致。富于挑戰(zhàn)的是:組合這些框架使得每一層都以一種松耦合的方式彼此溝通,而與底層的技術(shù)無關(guān)。這篇文章將使用3種流行的開源框架來討論組合框架的策略 
  其實(shí),就算用java建造一個(gè)不是很煩瑣的web應(yīng)用程序,也不是件輕松的事情。當(dāng)為一個(gè)應(yīng)用程序建造一個(gè)構(gòu)架時(shí)有許多事情需要考慮。從高層來說,開發(fā)者需要考慮:怎樣建立用戶接口?在哪里處理業(yè)務(wù)邏輯?和怎樣持久化應(yīng)用數(shù)據(jù)。這三層每一層都有它們各自的問題需要回答。 各個(gè)層次應(yīng)該使用什么技術(shù)?怎樣才能把應(yīng)用程序設(shè)計(jì)得松耦合和能靈活改變?構(gòu)架允許層的替換不會(huì)影響到其它層嗎?應(yīng)用程序怎樣處理容器級(jí)的服務(wù),比如事務(wù)處理? 
  當(dāng)為你的web應(yīng)用程序創(chuàng)建一個(gè)構(gòu)架時(shí),需要涉及到相當(dāng)多的問題。幸運(yùn)的是,已經(jīng)有不少開發(fā)者已經(jīng)遇到過這類重復(fù)發(fā)生的問題,并且建立了處理這類問題的框架。一個(gè)好框架具備以下幾點(diǎn): 減輕開發(fā)者處理復(fù)雜的問題的負(fù)擔(dān)(“不重復(fù)發(fā)明輪子”);內(nèi)部定義為可擴(kuò)展的;有一個(gè)強(qiáng)大的用戶群支持??蚣芡ǔD軌蚝芎玫慕鉀Q一方面的問題。然而,你的應(yīng)用程序有幾個(gè)層可能都需要它們各自的框架。就如解決你的用戶接口(ui)問題時(shí)你就不應(yīng)該把事務(wù)邏輯和持久化邏輯摻雜進(jìn)來。例如,你不應(yīng)該在控制器里面寫jdbc代碼,使它包含有業(yè)務(wù)邏輯,這不是控制器應(yīng)該提供的功能。它應(yīng)該是輕量級(jí)的,代理來自用戶接口(ui)外的調(diào)用請求給其它服務(wù)于這些請求的應(yīng)用層。好的框架自然的形成代碼如何分布的指導(dǎo)。更重要的是,框架減輕開發(fā)者從頭開始寫像持久層這樣的代碼的痛苦,使他們專注于對客戶來說很重要的應(yīng)用邏輯。
  這篇文章將討論怎樣組合幾個(gè)著名的框架去做到松耦合的目的,怎樣建立你的構(gòu)架,怎樣讓你的各個(gè)應(yīng)用層保持一致。富于挑戰(zhàn)的是:組合這些框架使得每一層都以一種松耦合的方式彼此溝通,而與底層的技術(shù)無關(guān)。這篇文章將使用3種流行的開源框架來討論組合框架的策略。表現(xiàn)層我們將使用struts;業(yè)務(wù)層我們將使用spring;持久層使用hibrenate.你也可以在你的應(yīng)用程序中替換這些框架中的任何一種而得到同樣的效果。圖1展示了當(dāng)這些框架組合在一起時(shí)從高層看是什么樣子。  
 
圖1 用struts, spring, 和 hibernate框架構(gòu)建的概覽 
  應(yīng)用程序的分層
  大多數(shù)不復(fù)雜的web應(yīng)用都能被分成至少4個(gè)各負(fù)其責(zé)的層次。這些層次是:表現(xiàn)層、持久層、業(yè)務(wù)層、領(lǐng)域模型層。每層在應(yīng)用程序中都有明確的責(zé)任,不應(yīng)該和其它層混淆功能。每一應(yīng)用層應(yīng)該彼此獨(dú)立但要給他們之間放一個(gè)通訊接口。讓我們從審視各個(gè)層開始,討論這些層應(yīng)該提供什么和不應(yīng)該提供什么。
  表現(xiàn)層
  在一個(gè)典型的web應(yīng)用的一端是表現(xiàn)層。很多java開發(fā)者也理解struts所提供的。然而,太常見的是,他們把像業(yè)務(wù)邏輯之類的耦合的代碼放進(jìn)了一個(gè)org.apache.struts.action。所以,讓我們在像struts這樣一個(gè)框架應(yīng)該提供什么上取得一致意見。這兒是struts負(fù)責(zé)的: 
  ·為用戶管理請求和響應(yīng); 
  ·提供一個(gè)控制器代理調(diào)用業(yè)務(wù)邏輯和其它上層處理; 
  ·處理從其它層擲出給一個(gè)struts action的異常; 
  ·為顯示提供一個(gè)模型; 
  ·執(zhí)行用戶接口驗(yàn)證。
  這兒是一些經(jīng)常用struts編寫的但是卻不應(yīng)該和struts表現(xiàn)層相伴的項(xiàng)目: 
  ·直接和數(shù)據(jù)庫通訊,比如jdbc調(diào)用; 
  ·業(yè)務(wù)邏輯和與你的應(yīng)用程序相關(guān)的驗(yàn)證; 
  ·事務(wù)管理;
  ·在表現(xiàn)層中引入這種代碼將導(dǎo)致典型耦合和討厭的維護(hù)。
  持久層
  在典型web應(yīng)用的另一端是持久層。這通常是使事情迅速失控的地方。開發(fā)者低估了構(gòu)建他們自己的持久層框架的挑戰(zhàn)性。一般來說,機(jī)構(gòu)內(nèi)部自己寫的持久層不僅需要大量的開發(fā)時(shí)間,而且還經(jīng)常缺少功能和變得難以控制。有幾個(gè)開源的“對象-關(guān)系映射”框架非常解決問題。尤其是,hibernate框架為java提供了"對象-關(guān)系持久化"機(jī)制和查詢服務(wù)。hibernate對那些已經(jīng)熟悉了sql和jdbc api的java開發(fā)者有一個(gè)適中的學(xué)習(xí)曲線。hibernate持久對象是基于簡單舊式j(luò)ava對象和java集合。此外,使用hibernate并不妨礙你正在使用的ide。下面的列表包含了你該寫在一個(gè)持久層框架里的代碼類型:
  查詢相關(guān)的信息成為對象。hibernate通過一種叫作hql的面向?qū)ο蟮牟樵冋Z言或者使用條件表達(dá)式api來做這個(gè)事情。 hql非常類似于sql-- 只是把sql里的table和columns用object和它的fields代替。有一些新的專用的hql語言成分要學(xué);不過,它們?nèi)菀桌斫舛椅臋n做得好。hql是一種使用來查詢對象的自然語言,花很小的代價(jià)就能學(xué)習(xí)它。
  保存、更新、刪除儲(chǔ)存在數(shù)據(jù)庫中的信息。 
  像hibernate這樣的高級(jí)“對象-關(guān)系”映射框架提供對大多數(shù)主流sql數(shù)據(jù)庫的支持,它們支持“父/子”關(guān)系、事務(wù)處理、繼承和多態(tài)。
  這兒是一些應(yīng)該在持久層里被避免的項(xiàng)目:
  業(yè)務(wù)邏輯應(yīng)該在你的應(yīng)用的一個(gè)高一些的層次里。持久層里僅僅允許數(shù)據(jù)存取操作。 
  你不應(yīng)該把持久層邏輯和你的表現(xiàn)層邏輯攪在一起。避免像jsps或基于servlet的類這些表現(xiàn)層組件里的邏輯和數(shù)據(jù)存取直接通訊。通過把持久層邏輯隔離進(jìn)它自己的層,應(yīng)用程序變得易于修改而不會(huì)影響在其它層的代碼。例如:hebernate能夠被其它持久層框架或者api代替而不會(huì)修改在其它任何層的代碼。 
  業(yè)務(wù)層
  在一個(gè)典型的web應(yīng)用程序的中間的組件是業(yè)務(wù)層或服務(wù)層。從編碼的視角來看,這個(gè)服務(wù)層是最容易被忽視的一層。不難在用戶接口層或者持久層里找到散布在其中的這種類型的代碼。這不是正確的地方,因?yàn)檫@導(dǎo)致了應(yīng)用程序的緊耦合,這樣一來,隨著時(shí)間推移代碼將很難維護(hù)。幸好,針對這一問題有好幾種frameworks存在。在這個(gè)領(lǐng)域兩個(gè)最流行的框架是spring和picocontainer,它們叫作微容器,你可以不費(fèi)力不費(fèi)神的把你的對象連在一起。所有這些框架都工作在一個(gè)簡單的叫作“依賴注入”(也通稱“控制反轉(zhuǎn)”)的概念上。這篇文章將著眼于spring的為指定的配置參數(shù)通過bean屬性的setter注入的使用。spring也提供了一個(gè)構(gòu)建器注入的復(fù)雜形式作為setter注入的一個(gè)替代。對象們被一個(gè)簡單的xml文件連在一起,這個(gè)xml文件含有到像事務(wù)管理器、對象工廠、包含業(yè)務(wù)邏輯的服務(wù)對象、和數(shù)據(jù)存取對象這些對象的引用。 
  這篇文章的后面將用例子來把spring使用這些概念的方法說得更清楚一些。業(yè)務(wù)層應(yīng)該負(fù)責(zé)下面這些事情:
  ·處理應(yīng)用程序的業(yè)務(wù)邏輯和業(yè)務(wù)驗(yàn)證; 
  ·管理事務(wù); 
  ·預(yù)留和其它層交互的接口; 
  ·管理業(yè)務(wù)層對象之間的依賴; 
  ·增加在表現(xiàn)層和持久層之間的靈活性,使它們互不直接通訊; 
  ·從表現(xiàn)層中提供一個(gè)上下文給業(yè)務(wù)層獲得業(yè)務(wù)服務(wù); 
  ·管理從業(yè)務(wù)邏輯到持久層的實(shí)現(xiàn)。 
  領(lǐng)域模型層
  最后,因?yàn)槲覀冇懻摰氖且粋€(gè)不是很復(fù)雜的、基于web的應(yīng)用程序,我們需要一組能在不同的層之間移動(dòng)的對象。領(lǐng)域?qū)ο髮佑赡切┐憩F(xiàn)實(shí)世界中的業(yè)務(wù)對象的對象們組成,比如:一份訂單、訂單項(xiàng)、產(chǎn)品等等。這個(gè)層讓開發(fā)者停止建立和維護(hù)不必要的數(shù)據(jù)傳輸對象(或者叫作dtos),來匹配他們的領(lǐng)域?qū)ο蟆@纾琱ibernate允許你把數(shù)據(jù)庫信息讀進(jìn)領(lǐng)域?qū)ο蟮囊粋€(gè)對象圖,這樣你可以在連接斷開的情況下把這些數(shù)據(jù)顯示到ui層。那些對象也能被更新和送回到持久層并在數(shù)據(jù)庫里更新。而且,你不必把對象轉(zhuǎn)化成dtos,因?yàn)閐tos在不同的應(yīng)用層間移動(dòng),可能在轉(zhuǎn)換中丟失。這個(gè)模型使得java開發(fā)者自然地以一種面向?qū)ο蟮娘L(fēng)格和對象打交道,沒有附加的編碼。
  結(jié)合一個(gè)簡單的例子
  既然我們已經(jīng)從一個(gè)高的層次上理解了這些組件, 現(xiàn)在就讓我們開始實(shí)踐吧。在這個(gè)例子中,我們還是將合并struts、spring、hibernate框架。每一個(gè)這些框架在一篇文章中都有太多的細(xì)節(jié)覆蓋到。這篇文章將用一個(gè)簡單的例子代碼展示怎樣把它們結(jié)合在一起,而不是進(jìn)入每個(gè)框架的許多細(xì)節(jié)。示例應(yīng)用程序?qū)⑹痉兑粋€(gè)請求怎樣跨越每一層被服務(wù)的。這個(gè)示例應(yīng)用程序的一個(gè)用戶能保存一個(gè)訂單到數(shù)據(jù)庫中和查看一個(gè)在數(shù)據(jù)庫中存在的訂單。進(jìn)一步的增強(qiáng)可以使用戶更新或刪除一個(gè)存在的訂單。
  因?yàn)轭I(lǐng)域?qū)ο髮⒑兔恳粚咏换?,我們將首先?chuàng)建它們。這些對象將使我們定義什么應(yīng)該被持久化,什么業(yè)務(wù)邏輯應(yīng)該被提供,和哪種表現(xiàn)接口應(yīng)該被設(shè)計(jì)。然后,我們將配置持久層和用hibernate為我們的領(lǐng)域?qū)ο蠖x“對象-關(guān)系”映射。然后,我們將定義和配置我們的業(yè)務(wù)對象。在有了這些組件后,我們就能討論用spring把這些層連在一起。最后,我們將提供一個(gè)表現(xiàn)層,它知道怎樣和業(yè)務(wù)服務(wù)層交流和知道怎樣處理從其它層產(chǎn)生的異常。
  領(lǐng)域?qū)ο髮?/strong>
  因?yàn)檫@些對象將和所有層交互,這也許是一個(gè)開始編碼的好地方。這個(gè)簡單的領(lǐng)域模型將包括一個(gè)代表一份訂單的對象和一個(gè)代表一個(gè)訂單項(xiàng)的對象。訂單對象將和一組訂單項(xiàng)對象有一對多的關(guān)系。例子代碼在領(lǐng)域?qū)佑袃蓚€(gè)簡單的對象:
  ·com.meagle.bo.order.java: 包括一份訂單的概要信息;
  ·com.meagle.bo.orderlineitem.java: 包括一份訂單的詳細(xì)信息;
  考慮一下為你的對象選擇包名,它將反映你的應(yīng)用程序是怎樣分層的。例如:簡單應(yīng)用的領(lǐng)域?qū)ο罂梢苑胚M(jìn)com.meagle.bo包。更多專門的領(lǐng)域?qū)ο髮⒎湃朐赾om.meagle.bo下面的子包里。業(yè)務(wù)邏輯在com.meagle.service包里開始打包,dao對象放進(jìn)com.meagle.service.dao.hibernate包。對于forms和actions的表現(xiàn)類分別放入com.meagle.action 和 com.meagle.forms包。準(zhǔn)確的包命名為你的類提供的功能提供一個(gè)清楚的區(qū)分,使當(dāng)故障維護(hù)時(shí)更易于維護(hù),和當(dāng)給應(yīng)用程序增加新的類或包時(shí)提供一致性。
  持久層配置
  用hibernate設(shè)置持久層涉及到幾個(gè)步驟。第一步是進(jìn)行配置持久化我們的領(lǐng)域業(yè)務(wù)對象。因?yàn)槲覀冇糜陬I(lǐng)域?qū)ο蟪志没膆ibernate和pojos一起工作,因此,訂單和訂單項(xiàng)對象包括的所有的字段的都需要提供getter和setter方法。訂單對象將包括像id、用戶名、合計(jì)、和訂單項(xiàng)這樣一些字段的標(biāo)準(zhǔn)的javabean格式的setter和getter方法。訂單項(xiàng)對象將同樣的用javabean的格式為它的字段設(shè)置setter和getter方法。
  hibernate在xml文件里映射領(lǐng)域?qū)ο蟮疥P(guān)系數(shù)據(jù)庫。訂單和訂單項(xiàng)對象將有兩個(gè)映射文件來表達(dá)這種映射。有像xdoclet這樣的工具來幫助這種映射。hibernate將映射領(lǐng)域?qū)ο蟮竭@些文件:
  order.hbm.xml 
  orderlineitem.hbm.xml
  你可以在webcontent/web-inf/classes/com/meagle/bo目錄里找到這些生成的文件。配置hibernate sessionfactory使它知道是在和哪個(gè)數(shù)據(jù)庫通信,使用哪個(gè)數(shù)據(jù)源或連接池,加載哪些持久對象。sessionfactory提供的session對象是java對象和像選取、保存、更新、刪除對象這樣一些持久化功能間的翻譯接口。我們將在后面的部分討論hibernate操作session對象需要的sessionfactory配置。
  業(yè)務(wù)層配置
  既然我們已經(jīng)有了領(lǐng)域?qū)ο?,我們需要有業(yè)務(wù)服務(wù)對象來執(zhí)行應(yīng)用邏輯、執(zhí)行向持久層的調(diào)用、獲得從用戶接口層的請求、處理事務(wù)、處理異常。為了將所有這些連接起來并且易于管理,我們將使用spring框架的bean管理方面。spring使用“控制反轉(zhuǎn)”,或者“setter依賴注入”來把這些對象連好,這些對象在一個(gè)外部的xml文件中被引用?!翱刂品崔D(zhuǎn)”是一個(gè)簡單的概念,它允許對象接受其它的在一個(gè)高一些的層次被創(chuàng)建的對象。使用這種方法,你的對象從必須創(chuàng)建其它對象中解放出來并降低對象耦合。
  這兒是個(gè)不使用ioc的對象創(chuàng)建它的從屬對象的例子,這導(dǎo)致緊的對象耦合:
  圖2:沒有使用ioc的對象組織。對象a創(chuàng)建對象b和c。
  這兒是一個(gè)使用ioc的例子,它允許對象在一個(gè)高一些層次被創(chuàng)建和傳進(jìn)另外的對象,所以另外的對象能直接使用現(xiàn)成的對象·[譯者注:另外的對象不必再親自創(chuàng)建這些要使用的對象]: 
 
圖3:對象使用ioc組織。對象a包含setter方法,它們接受到對象b和c的接口。這也可以用對象a里的接受對象b和c的構(gòu)建器完成。
  建立我們的業(yè)務(wù)服務(wù)對象
  我們將在我們的業(yè)務(wù)對象中使用的setter方法接受的是接口,這些接口允許對象的松散定義的實(shí)現(xiàn),這些對象將被設(shè)置或者注入。在我們這個(gè)例子里我們將使我們的業(yè)務(wù)服務(wù)對象接受一個(gè)dao去控制我們的領(lǐng)域?qū)ο蟮某志没?。?dāng)我們在這篇文章的例子中使用hibernate,我們可以容易的轉(zhuǎn)換到一個(gè)不同的持久框架的實(shí)現(xiàn),通知spring使用新的實(shí)現(xiàn)的dao對象。你能明白編程到接口和使用“依賴注入”模式是怎樣寬松耦合你的業(yè)務(wù)邏輯和你的持久化機(jī)制的。
  這兒是業(yè)務(wù)服務(wù)對象的接口,它是一個(gè)dao對象依賴的樁。
| public interface iorderservice { public abstract order saveneworder(order order) throws orderexception, orderminimumamountexception; public abstract list findorderbyuser(string user) throws orderexception; public abstract order findorderbyid(int id) throws orderexception; public abstract void setorderdao(iorderdao orderdao); } | 
| public interface iorderdao { public abstract order findorderbyid(final int id); public abstract list findordersplacebyuser(final string placedby); public abstract order saveorder(final order order); } | 
| <bean id="mysessionfactory" class="org.springframework.orm.hibernate. localsessionfactorybean"> ?。紁roperty name="mappingresources"> ?。糽ist> ?。紇alue> com/meagle/bo/order.hbm.xml </value> ?。紇alue> com/meagle/bo/orderlineitem.hbm.xml ?。?value> </list> </property> ?。紁roperty name="hibernateproperties"> <props> <prop key="hibernate.dialect"> net.sf.hibernate.dialect.mysqldialect ?。?prop> <prop key="hibernate.show_sql"> false </prop> ?。紁rop key="hibernate.proxool.xml"> c:/mywebapps/.../web-inf/proxool.xml ?。?prop> <prop key="hibernate.proxool.pool_alias"> spring </prop> ?。?props> </property> </bean> <!-- transaction manager for a single hibernate sessionfactory (alternative to jta) --> <bean id="mytransactionmanager" class="org. springframework. orm. hibernate. hibernatetransactionmanager"> <property name="sessionfactory"> <ref local="mysessionfactory"/> ?。?property> ?。?bean> | 
| <!-- order service --> <bean id="orderservice" class="org. springframework. transaction. interceptor. transactionproxyfactorybean"> ?。紁roperty name="transactionmanager"> ?。紃ef local="mytransactionmanager"/> </property> ?。紁roperty name="target"> ?。紃ef local="ordertarget"/> ?。?property> ?。紁roperty name="transactionattributes"> <props> ?。紁rop key="find*"> propagation_required,readonly,-orderexception ?。?prop> <prop key="save*"> propagation_required,-orderexception </prop> ?。?props> </property> </bean> <!-- order target primary business object: hibernate implementation --> <bean id="ordertarget" class="com. meagle. service. spring. orderservicespringimpl"> ?。紁roperty name="orderdao"> <ref local="orderdao"/> ?。?property> </bean> <!-- order dao object --> <bean id="orderdao" class="com. meagle. service. dao. hibernate. orderhibernatedao"> ?。紁roperty name="sessionfactory"> ?。紃ef local="mysessionfactory"/> </property> </bean> | 

  提供一個(gè)服務(wù)定位器
  既然我們已經(jīng)把我們的服務(wù)和我們的dao連起來了,我們需要把我們的服務(wù)暴露給其它層。通常是一個(gè)像使用struts或swing這樣的用戶接口層里的代碼來使用這個(gè)服務(wù)。一個(gè)簡單的處理方法是使用一個(gè)服務(wù)定位器模式的類從一個(gè)spring上下文中返回資源。這也可以靠引用bean id通過spring來直接完成。
  這兒是一個(gè)在struts action中怎樣配置一個(gè)服務(wù)定位器的例子:
| public abstract class baseaction extends action { private iorderservice orderservice; public void setservlet(actionservlet actionservlet) { super.setservlet(actionservlet); servletcontext servletcontext = actionservlet.getservletcontext(); webapplicationcontext wac = webapplicationcontextutils. getrequiredwebapplicationcontext( servletcontext); this.orderservice = (iorderservice) wac.getbean("orderservice"); } protected iorderservice getorderservice() { return orderservice; } } | 
| <action path="/saveneworder" type="com.meagle.action.saveorderaction" name="orderform" scope="request" validate="true" input="/neworder.jsp"> ?。糳isplay-name>save new order</display-name> <exception key="error.order.save" path="/neworder.jsp" scope="request" type="com.meagle.exception.orderexception"/> <exception key="error.order.not.enough.money" path="/neworder.jsp" scope="request" type="com. meagle. exception. orderminimumamountexception"/> ?。糵orward name="success" path="/vieworder.jsp"/> ?。糵orward name="failure" path="/neworder.jsp"/> </action> | 
| public actionforward execute( actionmapping mapping, actionform form, javax.servlet.http.httpservletrequest request, javax.servlet.http.httpservletresponse response) throws java.lang.exception { orderform oform = (orderform)form; // use the form to build an order object that // can be saved in the persistence layer. // see the full source code in the sample app. // obtain the wired business service object // from the service locator configuration // in baseaction. // delegate the save to the service layer and // further upstream to save the order object. getorderservice().saveneworder(order); oform.setorder(order); actionmessages messages = new actionmessages(); messages.add( actionmessages.global_message, new actionmessage( "message.order.saved.successfully")); savemessages(request, messages); return mapping.findforward("success"); } | 
新聞熱點(diǎn)
疑難解答
圖片精選