人人都愛Spring加Hibernate。
????但Spring MVC+hibernate的Sample如Appfuse的代碼卻算不得最簡(jiǎn)潔優(yōu)美好讀,如果在自己的項(xiàng)目中繼續(xù)發(fā)揮我們最擅長(zhǎng)的依樣畫葫蘆大法,美好愿望未必會(huì)實(shí)現(xiàn)。
???? 所以,Pramatic精神不滅。這個(gè)系列就是探尋最適合自己的Spring+Hibernate模式。
????
?????????????????????????????
I-配置文件簡(jiǎn)化?????我厭倦一切配置文件繁重的框架。
???? 最好的情況是,框架提供極端靈活復(fù)雜的配置方式,但只在你需要的時(shí)候。
?
???? Spring提供了三種可能來簡(jiǎn)化XML。隨著國內(nèi)用戶水平的提高,這些基本的簡(jiǎn)化技巧大家都已掌握。
?????大家可以直接看第3,第4點(diǎn)--Spring 1.2, Spring 2.0的后繼改進(jìn)。
1.1.autowire="byName" /"byType"
???? 假設(shè)Controller有一個(gè)屬性名為customerDAO,Spring就會(huì)在配置文件里查找有沒有名字為CustomerDAO的bean, 自動(dòng)為Controller注入。
?????如果bean有兩個(gè)屬性,一個(gè)想默認(rèn)注入,一個(gè)想自定義,只要設(shè)定了autowire,然后顯式的聲明那個(gè)想自定義的,就可以達(dá)到要求。這就應(yīng)了需求,在需要特別配置的時(shí)候就提供配置,否則給我一個(gè)默認(rèn)注入。
???? 還有一個(gè)更懶的地方,在最最根部的<beans>節(jié)點(diǎn)寫一句default-autovwrie="byName",可以讓文件里的所有bean 都默認(rèn)autowrie。
??? 不過Rod認(rèn)為開發(fā)期可以這樣,但Production Server上不應(yīng)該使用Autowire。而我覺得那些自定義一次的地方比如TranscationManager應(yīng)該詳細(xì)定義,而Dao,Controller這種大量重復(fù)定義的bean就可以偷點(diǎn)懶了。
1.2.<bean>節(jié)點(diǎn)之間抽象公共定義和 Inner Bean
??? 這太方便懶人了,想不到兩個(gè)獨(dú)立的XML節(jié)點(diǎn)都可以玩繼承和派生,子節(jié)點(diǎn)擁有父節(jié)點(diǎn)的全部屬性。
??? 最好用的地方就是那個(gè)Transtion Proxy的定義。先定義一個(gè)又長(zhǎng)又冗的父類,然后用子類去繼承它。
???
??? 另外,還有一個(gè)Inner Bean的機(jī)制,可以把DAO寫成Proxy的內(nèi)部類。為什么要寫成內(nèi)部類?為了讓Proxy冒名頂替它去讓Controller Autowire。(詳見后面的示例)
1.3. 寬松的配置, To XML or Not to XML?
??? 據(jù)說Spring比Struts的配置寬松了很多,這就給人把東西從配置文件中撤回原碼中的機(jī)會(huì)。
??? 不贊成什么都往配置文件里曬,造成了Rich Information的配置文件,修改或者查看的時(shí)候,要同時(shí)打開配置文件和原碼才能清楚一切。
??? 而我希望配置文件就集中做一些整體的配置,還有框架必須的、無需管理的冗余代碼。而一些細(xì)節(jié)的變化不大的配置和邏輯,就盡量別往里塞了。因此,Success/Fail View 的配置,不建議放在里面。
2.簡(jiǎn)化后的配置文件
1.Controller只剩下一句
<bean?name="customerController"?class="org.springside.bookstore.web.CustomerController"?autowire="byName"/>
2.DAO也只剩一句
<bean?id="customerDAO"?class="org.springside.bookstore.dao.CustomerDao"/>
3.Service類只剩下5行
??<bean?id="customerManager"?parent="baseTxService">
????????<property?name="target">
????????????<bean?class="org.springside.bookstore.service.CustomerManager"/>
????????</property>
????</bean>
3.Spring 1.2后xml語法簡(jiǎn)化
?最主要的簡(jiǎn)化是把屬性值和引用bean從子節(jié)點(diǎn)變回了屬性值,對(duì)不喜歡autowire的兄弟比較有用。
?當(dāng)然,如果value要CDATA的時(shí)候還是要用子節(jié)點(diǎn)。另外,list的值可以用空格隔開也比較實(shí)用。
?
<property?name="myFriendList">
??<list>
?????<value>gigix</value>
??????? <value>wuyu</value>
??</list>
</property>
簡(jiǎn)化為
<property?name="myFriendList"?value="gigix wuyu"/>

???
??
4.Spring 2.0來了?? 如果沒什么外力刺激,spring xml 可能就這樣不會(huì)變了。但現(xiàn)在xml成了過街老鼠,被ror的默認(rèn)配置和JDK5的annotation逼得不行,當(dāng)然就要繼續(xù)求變。
?? 比如有好事者認(rèn)為,節(jié)點(diǎn)名必須以bean打頭,附加一個(gè)屬性id來表示bean名;屬性值必須搞一個(gè)property子節(jié)點(diǎn),子節(jié)點(diǎn)上有個(gè)屬性name來表示屬性名,是給機(jī)器看的很不直觀的東西。
<bean?id="customerDAO" class="org.springside...CustomerDAO">
?<property?name="maxCount"?value="10">
</bean>
給人看的東西應(yīng)該就寫成
<customerDAO?class="org.springside....CustomerDAO"?maxCount="10"/>
Spring 2.0正用schema實(shí)現(xiàn)類似的語法,具體請(qǐng)看它的JPetStore sample。
5.使用Spring自帶的DTD使編輯器Smart.
??? 如果沒有用Eclipse的Spring插件,那至少也要使用spring自帶的dtd使XML編輯器smart一些,能夠自動(dòng)為你生成屬性,判斷節(jié)點(diǎn)/屬性名稱有沒有拼錯(cuò)等。
6.還有更變態(tài)的簡(jiǎn)化配置方法
?? ?比如autoproxy,不過我覺得更簡(jiǎn)化就不可控了,所以沒有采用。
因?yàn)镾pring自帶的sample離我們的實(shí)際項(xiàng)目很遠(yuǎn),所以官方一點(diǎn)的model層模式展現(xiàn)就靠Appfuse了。
????但Appfuse的model層總共有一個(gè)DAO接口、一個(gè)DAOImpl類、一個(gè)Service接口、一個(gè)ServiceImpl類、一個(gè)DataObject.....大概只有受慣了虐待的人才會(huì)欣然接受吧。
??? 另外,Domain-Driven逢初一、十五也會(huì)被拿出來討論一遍。
??? 其實(shí)無論什么模式,都不過是一種人為的劃分、抽象和封裝。只要在團(tuán)隊(duì)里理解一致,自我感覺優(yōu)雅就行了。
???? 我的建議是,一開始DO和Manager一生一旦包演全場(chǎng),DO作為純數(shù)據(jù)載體,而Manager類放置商業(yè)方法,用getHibernateTemplate()直接訪問數(shù)據(jù)庫,不強(qiáng)制基于接口編程。當(dāng)某天系統(tǒng)復(fù)雜到你直覺上需要將DAO層和Service層分開時(shí),再分開就好了。
??? 1.DataObject類
???? 好聽點(diǎn)也可以叫Domain Object。Domain Driven? Development雖然誘人,但因?yàn)镴ava下的ORM框架都是基于Data Mapper模式的,沒有Ruby On Rails中那種Active Recorder的模式。所以,還是壓下了這個(gè)欲望,Data Object純粹作一個(gè)數(shù)據(jù)載體,而把數(shù)據(jù)庫訪問與商業(yè)邏輯操作統(tǒng)一放到Manager類中。
??? 2.Manager類
????我的Manager類是Appfuse中DAO類與Service類的結(jié)合體,因?yàn)椋?br />
????2.1 不想使用純DAO
???? 以往的DAO是為了透明不同數(shù)據(jù)庫間的差異,而現(xiàn)在Hibernate已經(jīng)做的很好。所以目前純DAO的更大作用是為了將來可以切換到別的ORM方案比如iBatis,但一個(gè)Pragmaic的程序員顯然不會(huì)無聊到為了這個(gè)機(jī)會(huì)不大的理由,現(xiàn)在就去做一個(gè)純DAO層,項(xiàng)目又不是Appfuse那樣為了demo各種ORM方案而存在。
??? 2.2 也不使用純的薄Service層
??? 在JPetStore里有一個(gè)很薄的Service層,F(xiàn)ascade了一堆DAO類,把這些DAO類的所有方法都僵硬的重復(fù)了一遍。而我認(rèn)為Fascade的意義在二:
????一是Controller調(diào)用Manager甲的時(shí)候,總會(huì)伴隨著調(diào)用Manager乙的某些方法。使用Fascade可以避免Controller零散的調(diào)用一堆Manager類。
????二是一個(gè)商業(yè)過程里可能需要同時(shí)調(diào)用DAO甲乙丙丁的方法。?
?????這些時(shí)候,F(xiàn)ascade都是合理的。但我討厭類膨脹,所以我寧愿在甲乙丙丁中挑一個(gè)來充當(dāng)Fascade的角色。有耦合的問題嗎?對(duì)一個(gè)不是死搬書的Designer來說,組件邊界之內(nèi)的類之間的耦合并不是耦合。
??? 3.去除不必要的基于接口編程
??? 眾所周知,Spring是提倡基于接口編程的。
??? 但有些Manager類,比如SaleOrderManager ,只有5%的機(jī)會(huì)再有另一個(gè)Impl實(shí)現(xiàn)。95%時(shí)間里這兩兄弟站一起,就像C++里的.h和.cpp,徒增維護(hù)的繁瑣(經(jīng)常要同步兩個(gè)文件的函數(shù)聲明),和代碼瀏覽跳轉(zhuǎn)時(shí)的不便(比如從Controler類跟蹤到Service類時(shí),只能跳轉(zhuǎn)到接口類的相應(yīng)函數(shù),還要再按一次復(fù)雜的熱鍵才跳轉(zhuǎn)到實(shí)現(xiàn)類)
??? 連Martin Flower都說,強(qiáng)制每個(gè)類都分離接口和實(shí)現(xiàn)是過猶不及。只在有多個(gè)獨(dú)立實(shí)現(xiàn),或者需要消除對(duì)實(shí)現(xiàn)類的依賴時(shí),才需要分離接口。
????3.1 DAO被強(qiáng)制用接口的原因
??? Spring IOC本身是不會(huì)強(qiáng)制基于接口的,但DAO類一般要使用Spring的聲明式事務(wù)機(jī)制,而聲明式的事務(wù)機(jī)制是使用Spring AOP來實(shí)現(xiàn)的。Spring AOP的實(shí)現(xiàn)機(jī)制包括動(dòng)態(tài)代理和Cgilib2,其中Spring AOP默認(rèn)使用的Java動(dòng)態(tài)代理是必須基于接口,所以就要求基于接口了。
????
??? 3.2 解決方法
??? 那就讓Spring AOP改用CGLib2,生成目標(biāo)類的子類吧,我們只要指定使用聲明式事務(wù)的FactoryBean使用CGLib的方式來實(shí)現(xiàn)AOP,就可以不基于接口編程了。
??? 指定的方式為設(shè)置proxyTargetClass為true。如下:
<bean?class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
id="baseService"???abstract="true">
??<property?name="transactionManager"?ref="transactionManager"/>
??<property?name="proxyTargetClass"?value="true"/>


</bean>
???? 又因?yàn)檫@些Service Bean都是單例,效率應(yīng)該不受影響。
??? 4.總結(jié)
??? 對(duì)比Appfuse里面的5個(gè)類,我的Model層里只有VO作為純數(shù)據(jù)載體,Manager類放商業(yè)方法。有人說這樣太簡(jiǎn)單了,但一個(gè)應(yīng)用,要?jiǎng)澇蓭讉€(gè)JSP,一個(gè)Controller,一個(gè)Manager,一個(gè)VO,對(duì)我來說已經(jīng)足夠復(fù)雜,再要往上架墻疊屋,恕不奉陪,起碼在我的項(xiàng)目范圍里不需要。(但有很多項(xiàng)目是需要的,神佑世人)
??? 后記:迫于世人的壓力,SpringSide還是把DAO和Service層分開了,但依然堅(jiān)持不搞那么多接口。
Struts與Webwork的扇子請(qǐng)?zhí)^本篇。
??? MVC不就是把M、V、C分開么?至唯物樸素的做法是兩個(gè)JSP一個(gè)負(fù)責(zé)View,一個(gè)負(fù)責(zé)Controller,再加一個(gè)負(fù)責(zé)Model的Java Bean,已經(jīng)可以工作得很好,那時(shí)候一切都很簡(jiǎn)單。
??? 而現(xiàn)在為了一些不是本質(zhì)的功能,冒出這么多非標(biāo)準(zhǔn)的Web框架,實(shí)在讓人一陣郁悶。像Ruby On Rails那樣簡(jiǎn)捷開發(fā),可用可不用,而且沒有太多的限制需要學(xué)習(xí)的,比如Webwork這型還可以考慮。但像Struts那樣越用框架越麻煩,或者像Tapestry那樣有嚴(yán)重自閉傾向,額上鑿著"高手專用玩具"的,用在團(tuán)隊(duì)里就是不負(fù)責(zé)任的行為了。
????so,我的MVC方案是使用Spring MVC的Controller接口,寫最普通的JavaBean作為Controller,本質(zhì)就和當(dāng)年拿JSP作Controller差不多,但擁有了Spring IOC的特性。
????之所以用這么消極的選擇標(biāo)準(zhǔn),是因?yàn)橛X得這一代MVC框架離重回RAD時(shí)代的標(biāo)準(zhǔn)還很遠(yuǎn),注定了只是一段短暫的,過渡的技術(shù),不值得投資太多精力和團(tuán)隊(duì)學(xué)習(xí)成本。
1. 原理???? Spring MVC按植物分類學(xué)屬于Martin Flower〈企業(yè)應(yīng)用模式〉里的靜態(tài)配置型Front Controler,使用DispatchServlet截獲所有*.do的請(qǐng)求,按照xml文件的配置,調(diào)用對(duì)應(yīng)的Command對(duì)象的handleRequest(request,response)函數(shù),同時(shí)進(jìn)行依賴對(duì)象的注入。
???? 我們的Controller層,就是實(shí)現(xiàn)handleRequest(request,response)函數(shù)的普通JavaBean。
2. 優(yōu)勢(shì)
???? Spring MVC與struts相比的優(yōu)勢(shì):
?????一是它的Controller有著從松到緊的類層次結(jié)構(gòu),用戶可以選擇實(shí)現(xiàn)只有一個(gè)HandleRequest()函數(shù)的接口,也可以使用它有很多回調(diào)函數(shù)的SimpleFormController類。
?????二是不需要Form Bean,也不需要Tapestry那所謂面向?qū)ο蟮捻撁鎸?duì)象,對(duì)于深怕類膨脹,改一個(gè)東西要?jiǎng)覰個(gè)地方的人最適合不過。
?????三是不需要強(qiáng)XML配置文件,宣告式編程是好的,但如果強(qiáng)制成框架,什么都要在xml里面宣告,寫的時(shí)候繁瑣,看的時(shí)候也要代碼配置兩邊看才能明白就比較麻煩了。
?
?????那Webwork呢?沒有實(shí)戰(zhàn)過,不過因?yàn)閷?duì)MVC框架所求就不多,單用Spring MVC的Controller已經(jīng)可以滿足需求,就不多搞一套Webwork來給團(tuán)隊(duì)設(shè)坎,還有給日后維護(hù),spring,ww2之間的版本升級(jí)添麻煩了。真有什么需要添加的,Spring MVC源代碼量很少,很容易掌控和擴(kuò)展。
?
3.化簡(jiǎn)
3.1. 直接implement Controller,實(shí)現(xiàn)handleRequest()函數(shù)
??????首先,simple form controller非我所好,一點(diǎn)都不simple。所以有時(shí)我會(huì)直接implement Controller接口。這個(gè)接口的唯一函數(shù)是供Front Controller調(diào)用的handleRequest(request,response)。
????? 如果需要application對(duì)象,比如想用application.getRealPath()時(shí),就要extends webApplicationObjectSupport。
3.2.每個(gè)Controler負(fù)責(zé)一組相關(guān)的action
?????? 我是堅(jiān)決支持一個(gè)Controler負(fù)責(zé)多個(gè)action的,一個(gè)Controler一個(gè)action就像一個(gè)function一個(gè)類一樣無聊。所以我用最傳統(tǒng)的方式,用URL參數(shù)如msg="insert"把一組相關(guān)action交給一個(gè)Controler控制。ROR與制作中的Groovy On Rails都是這種模式,Spring也有MultiActionController支持。
?????? 以上三者都是把URL參數(shù)直接反射為Controller的函數(shù),而
Stripes的設(shè)計(jì)可用annotation標(biāo)注url action到響應(yīng)函數(shù)的映射。
????我的取舍很簡(jiǎn)單,反正Spring沒有任何強(qiáng)制,我只在可能需要不重新編譯而改變某些東西的時(shí)候,才把東西放在xml里動(dòng)態(tài)注入。jsp路徑之類的就統(tǒng)統(tǒng)收回到controller里面定義.
?
3.4.Data Binder
?????? Data Binder是Controller的必有環(huán)節(jié),對(duì)于Spring提供的DataBinder,照理完全可用,唯一不爽是對(duì)象如果有內(nèi)嵌對(duì)象,如訂單對(duì)象里面包含了Customer對(duì)象,Spring需要你先自行創(chuàng)建了Customer對(duì)象并把它賦給了Order對(duì)象,才可能實(shí)現(xiàn)order.customer.customer_no這樣的綁定。我偷懶,又拿Jakarta BeanUtils出來自己做了一個(gè)Binder。
3.5.提取基類
????? 最后還是忍不住提取了一個(gè)基類,負(fù)責(zé)MultiAction和其他一些簡(jiǎn)便的方法。Sprnig的MultiActionController做得太死,規(guī)定所有函數(shù)的第1,2個(gè)參數(shù)必須是request和response,不懂動(dòng)態(tài)的,溫柔的進(jìn)行參數(shù)注入。
??????
???? ?經(jīng)過化簡(jiǎn)再化簡(jiǎn),已經(jīng)是很簡(jiǎn)單一個(gè)Java Bean ,任誰都可以輕松上手,即使某年某月技術(shù)的大潮把現(xiàn)在所有MVC框架都淹沒了,也不至于沒人識(shí)得維護(hù)。
人生像個(gè)舞臺(tái),請(qǐng)良家少女離開。
????同樣的,F(xiàn)reemarker和Velocity愛好者請(qǐng)?zhí)^本篇。與棄用webwork而單用Spring MVC Controller接口的理由一樣,
Freemarker本來是一樣好東西,還跨界支持jsp?的taglib,而且得到了WebWork的全力支持,但為了它的非標(biāo)準(zhǔn)化,用戶數(shù)量與IDE的缺乏,在View層我們還是使用了
保守但人人會(huì)用,IDE友好的JSP2.0 配合JSTL。
??? 對(duì)于B/S結(jié)構(gòu)的企業(yè)應(yīng)用軟件來說,基本的頁面不外兩種,一種是填Form的,一種是DataGrid 數(shù)據(jù)列表管理的,再配合一些css, js, ajax的效果,就是View層要關(guān)注的東西了。
1. JSP 2.0的EL代替<c:out>JSP2.0可以直接把EL寫在html部分,而不必動(dòng)用<c:out>節(jié)點(diǎn)后,老實(shí)說,JSP2.0+JSTL達(dá)到的頁面效果,已不比Velocity相差多少了。
<p>{goods.name}</p>?
代替
<p><c:out?value="{goods.name}"/></p>
(除了EL里面不能調(diào)用goods的函數(shù),sun那幫老頑固始終堅(jiān)持JSTL只能用于數(shù)據(jù)顯示,不能進(jìn)行數(shù)據(jù)操作,所以不能調(diào)用bean的get/set外的方法)
?2. 最懶的form 數(shù)據(jù)綁定
??? Spring少得可憐的幾個(gè)tag基本上是雞肋,完全可以不要。 而Spring開發(fā)中的那些Simple Form tag又還沒有發(fā)布。Spring的Tag主要用來把VO的值綁到input框上。但是,和Struts一樣,需要逐個(gè)Input框綁定,而且語法極度冗長(zhǎng),遇到select框還要自己進(jìn)行處理.....典型的Spring Sample頁面讓人一陣頭暈.
??? 而jodd的form tag給了我們懶人一個(gè)懶得多的方法,只要在<form>兩頭用<jodd:form bean="myVO"></jodd:form>包住,里面的所有input框,select框,checkBox...統(tǒng)統(tǒng)自動(dòng)被綁定了,這么簡(jiǎn)單的事情,真不明白struts,spring為什么不用,為了不必要的靈活性么?
<form>
<jodd:form?bean="human">
<input?type="text"?name="name">
<input?type="radiobox"?name="sex"?value="man">
<select?name="age">
??<option?value="20">20</option>
??<option?value="30">30</option>
</select>
</jodd:form>
</form>?
不過,jodd有個(gè)致命弱點(diǎn)是不能綁定內(nèi)嵌對(duì)象的值。比如Order(訂單)對(duì)象里有個(gè)Customer(顧客)對(duì)象,jodd就不能像 struts,spring一樣用如下語法綁定:
<input?name="customer.customerNo">這是因?yàn)樗腷eanUtils比Jakata Common弱,用了一個(gè)錯(cuò)誤的思路的緣故。 動(dòng)用beanUtils修改一下就可以了,修改后的源碼可以在這里下載。
3. DataGrid數(shù)據(jù)列表
DisplayTag和ValueList都屬于這種形式的Tag Library。但最近出現(xiàn)的Extreme Table是真正的killer,他本身功能強(qiáng)大不說,而且從一開始就想著如何讓別人進(jìn)行擴(kuò)展重載,比如Extend Attributes機(jī)制就是DisplayTag這樣的讓千人一面者不會(huì)預(yù)留。
4.css, java script, ajax
天下紛擾,沒有什么特別想講想推薦的,愛誰誰吧。Buffalo, DWR, Scriptaculous, Prototype, AjaxTags, AjaxAnywhere, Rico, Dojo, JSON-RPC,看著名字就頭痛。