序
人人都愛Spring加Hibernate。
但Spring
MVC+hibernate的Sample如Appfuse的代碼卻遠算不得最簡潔優美好讀,如果在自己的項目中繼續發揮我們最擅長的依樣畫葫蘆大法,美好愿望未必會實現。
所以,Pramatic精神不滅。這個系列就是探尋最適合自己的Spring+Hibernate模式。
I-配置文件簡化
我厭倦一切配置文件繁重的框架。
最好的情況是,框架提供極端靈活復雜的配置方式,但只在你需要的時候。
Spring提供了三種可能來簡化XML。隨著國內用戶水平的提高,這些基本的簡化技巧大家都已掌握,從Spring 1.2開始又有了小小的改進,<Spring
XML: way simpler since 1.2>作了集大成的歸納
1.1.autowire="byName" /"byType"
假設Controller有一個屬性名為customerDAO,Spring就會在配置文件里查找有沒有名字為CustomerDAO的bean,
自動為Controller注入。
如果bean有兩個屬性,一個想默認注入,一個想自定義,只要設定了autowire,然后顯式的聲明
那個想自定義的,就可以達到要求。這就應了需求,在需要特別配置的時候就提供配置,否則給我一個默認注入可以了。
還有一個更懶的地方,在最最根部的<beans>節點寫一句default-autovwrie="byName",可以讓所有bean
都默認autowrie。
不過Rod認為開發期可以這樣,但Production
Server上不應該使用Autowire,大家自己來決定了。
1.2.<bean>節點之間抽象公共定義和 Inner Bean
這太方便懶人了,想不到兩個獨立的XML節點都可以玩繼承和派生,子節點擁有父節點的全部屬性。
最好用的地方就是那個Transtion
Proxy的定義。先定義一個又長又冗的父類,然后用子類去繼承它。
另外,還有一個Inner
Bean的機制,可以把DAO寫成Proxy的內部類。為什么要寫成內部類?為了讓Proxy冒名頂替它去讓Controller Autowire。(詳見后面的示例)
1.3. 寬松的配置, To XML or Not to XML
據說Spring比Struts的配置寬松了很多,這就給人把東西從配置文件中撤回原碼中的機會。
不贊成什么都往配置文件里曬,造成了Rich
Information的配置文件,修改或者查看的時候,要同時打開配置文件和原碼才能清楚一切。
而我希望配置文件就集中做一些整體的配置,還有框架必須的、無需管理的冗余代碼。而一些細節的變化不大的配置和邏輯,就盡量別往里塞了。因此,Success/Fail
View 的配置,不建議放在里面。
2.簡化后的配置文件
1.Controller只剩下一句
<bean id="saleOrderController" class="com.itorgan.web.SaleOrderController" />
2.DAO只剩下5行
<bean id="saleOrderDAO" parent="baseDAOService" >
<property name="target">
<bean class="com.itorgan.lherp.dao.impl.SaleOrderDAOImpl" />
</property>
</bean>
3.Spring
1.2后xml語法簡化
最主要的簡化是把屬性值和引用bean從子節點變回了屬性值,對不喜歡autowire的兄弟比較有用。
當然,如果value要CDATA的時候還是要用子節點。
1.屬性值
<property name="foo">
<value>fooValue</value>
</property>
簡化為
<property name="foo" value="fooValue"/>
2.引用
bean
<property name="foo">
<ref bean="fooBean">
</property>
簡化為
<property name="foo" value="fooBean"/>
3.
list如果只有一個值時,可以直接用value表示
<property name="myFriendList">
<list>
<value>wuyu</value>
</list>
</property>
簡化為
<property name="myFriendList" value="wuyu"/>
因為只有一個值時,List就退化為Object類型嘛,多體貼。
4.使用Spring自帶的DTD使編輯器Smart.
如果沒有用Eclipse的Spring插件,那至少也要使用spring自帶的dtd使XML編輯器smart一些,能夠自動為你生成屬性,判斷節點/屬性名稱有沒有拼錯等。
5.還有更變態的簡化配置方法
比如autoproxy,不過我覺得更簡化就不可控了,所以沒有采用。
II-Model層簡化
因為Spring自帶的sample離我們的實際項目很遠,所以官方一點的model層模式展現就靠Appfuse了。
但Appfuse的model層總共有一個DAO接口、一個DAOImpl類、一個Service接口、一個ServiceImpl類、一個DataObject.....大概只有受慣了虐待的人才會欣然接受吧。
另外,Domain-Driven逢初一、十五也會被拿出來討論一遍。
其實無論什么模式,都不過是一種人為的劃分、抽象和封裝。只要在團隊里理解一致,自我感覺優雅就行了。在我的Model層里,DO和Manager一生一
旦包演全場,VO作為純數據載體,而Manager類放置商業方法,用getHibernateTemplate()直接訪問數據庫,不強制基于接口編
程......
1.DataObject類
好聽點也可以叫Domain
Object。Domain Driven Development雖然誘人,但因為Java下的ORM框架都是基于Data Mapper模式的,沒有Ruby On
Rails中那種Active Recorder的模式。所以,還是壓下了這個欲望,Data
Object純粹作一個數據載體,而把數據庫訪問與商業邏輯操作統一放到Manager類中。
2.Manager類
我的Manager類是Appfuse中DAO類與Service類的結合體,因為:
2.1
不想使用純DAO
以往的DAO是為了透明不同數據庫間的差異,而現在Hibernate已經做的很好。所以目前純DAO的更大作用是為了將來可以切換到別的ORM方案比如
iBatis,但一個Pragmaic的程序員顯然不會無聊到為了這個機會不大的理由,現在就去做一個純DAO層,項目又不是Appfuse那樣為了
demo各種ORM方案而存在。
2.2 也不使用純的薄Service層
在JPetStore里有一個很薄的Service層,Fascade了一堆DAO類,把這些DAO類的所有方法都僵硬的重復了一遍。而我認為Fascade的意義在二:
一是Controller調用Manager甲的時候,總會伴隨著調用Manager乙的某些方法。使用Fascade可以避免Controller零散的調用一堆Manager類。
二是一個商業過程里可能需要同時調用Manager甲乙丙丁的方法。
這些時候,Fascade都是合理的。但我討厭類膨脹,所以我寧愿在甲乙丙丁中挑一個來充當
Fascade的角色。有耦合的問題嗎?對一個不是死搬書的Designer來說,組件邊界之內的類之間的耦合并不是耦合。
3.去除不必要的基于接口編程
眾所周知,Spring是提倡基于接口編程的。
但有些Manager類,比如SaleOrderManager
,只有5%的機會再有另一個Impl實現。95%時間里這兩兄弟站一起,就像C++里的.h和.cpp,徒增維護的繁瑣(經常要同步兩個文件的函數聲
明),和代碼瀏覽跳轉時的不便(比如從Controler類跟蹤到Service類時,只能跳轉到接口類的相應函數,還要再按一次復雜的熱鍵才跳轉到實現
類)
連Martin
Flower都說,強制每個類都分離接口和實現是過猶不及。只在有多個獨立實現,或者需要消除對實現類的依賴時,才需要分離接口。
3.1
DAO被強制用接口的原因
Spring
IOC本身是不會強制基于接口的,但DAO類一般要使用Spring的聲明式事務機制,而聲明式的事務機制是使用Spring AOP來實現的。Spring
AOP的實現機制包括動態代理和Cgilib2,其中Spring
AOP默認使用的Java動態代理是必須基于接口,所以就要求基于接口了。
3.2
解決方法
那就讓Spring
AOP改用CGLib2,生成目標類的子類吧,我們只要指定使用聲明式事務的FactoryBean使用CGLib的方式來實現AOP,就可以不基于接口編程了。
指定的方式為設置proxyTargetClass為true。如下:
<bean class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
id="baseService" abstract="true">
<property name="transactionManager" ref="transactionManager"/>
<property name="proxyTargetClass" value="true"/>
</bean>
又因為這些Service
Bean都是單例,效率應該不受影響。
4.總結
對比Appfuse里面的5個類,我的Model層里只有VO作為純數據載體,Manager類放商業方法。有人說這樣太簡單了,但一個應用,要劃成幾個
JSP,一個Controller,一個Manager,一個VO,對我來說已經足夠復雜,再要往上架墻疊屋,恕不奉陪,起碼在我的項目范圍里不需要。
(但有很多項目是需要的,神佑世人)
III-Controller層簡化
Struts與Webwork的扇子請跳過本篇。
MVC不就是把M、V、C分開么?至唯物樸素的做法是兩個JSP一個負責View,一個負責Controller,再加一個負責Model的Java
Bean,已經可以工作得很好,那時候一切都很簡單。
而現在為了一些不是本質的功能,冒出這么多非標準的Web框架,實在讓人一陣郁悶。像Ruby On
Rails那樣簡捷開發,可用可不用,而且沒有太多的限制需要學習的,比如Webwork這型還可以考慮。但像Struts那樣越用框架越麻煩,或者像Tapestry那樣有嚴重自閉傾向,額上鑿著"高手專用玩具"的,用在團隊里就是不負責任的行為了。
so,我的MVC方案是使用Spring
MVC的Controller接口,寫最普通的JavaBean作為Controller,本質就和當年拿JSP作Controller差不多,但擁有了Spring
IOC的特性。
之所以用這么消極的選擇標準,是因為覺得這一代MVC框架離重回RAD時代的標準還很遠,注定了只是一段短暫的,過渡的技術,不值得投資太多精力和團隊學習成本。
1. 原理
Spring MVC按植物分類學屬于Martin Flower〈企業應用模式〉里的靜態配置型Front
Controler,使用DispatchServlet截獲所有*.do的請求,按照xml文件的配置,調用對應的Command對象的handleRequest(request,response)函數,同時進行依賴對象的注入。
我們的Controller層,就是實現handleRequest(request,response)函數的普通JavaBean。
2. 優勢
Spring MVC與struts相比的優勢:
一是它的Controller有著從松到緊的類層次結構,用戶可以選擇實現只有一個HandleRequest()函數的接口,也可以使用它有很多回調函數的SimpleFormController類。
二是不需要Form
Bean,也不需要Tapestry那所謂面向對象的頁面對象,對于深怕類膨脹,改一個東西要動N個地方的人最適合不過。
三是不需要強XML配置文件,宣告式編程是好的,但如果強制成框架,什么都要在xml里面宣告,寫的時候繁瑣,看的時候也要代碼配置兩邊看才能明白就比較麻煩了。
那Webwork呢?沒有實戰過,不過因為對MVC框架所求就不多,單用Spring
MVC的Controller已經可以滿足需求,就不多搞一套Webwork來給團隊設坎,還有給日后維護,spring,ww2之間的版本升級添麻煩了。真有什么需要添加的,Spring
MVC源代碼量很少,很容易掌控和擴展。
3.化簡
3.1. 直接implement Controller,實現handleRequest()函數
首先,simple form controller非我所好,一點都不simple。所以有時我會直接implement
Controller接口。這個接口的唯一函數是供Front
Controller調用的handleRequest(request,response)。
如果需要application對象,比如想用application.getRealPath()時,就要extends
webApplicationObjectSupport。
3.2.每個Controler負責一組相關的action
我是堅決支持一個Controler負責多個action的,一個Controler一個action就像一個function一個類一樣無聊。所以我用
最傳統的方式,用URL參數如msg="insert"把一組相關action交給一個Controler控制。ROR與制作中的Groovy On
Rails都是這種模式,Spring也有MultiActionController支持。
以上三者都是把URL參數直接反射為Controller的函數,而Stripes的設計可用annotation標注url
action到響應函數的映射。
3.3.xml宣告式編程的取舍
我的取舍很簡單,反正Spring沒有任何強制,我只在可能需要不重新編譯而改變某些東西的時候,才把東西放在xml里動態注入。jsp路徑之類的就統統收回到controller里面定義.
3.4.Data Binder
Data
Binder是Controller的必有環節,對于Spring提供的DataBinder,照理完全可用,唯一不爽是對象如果有內嵌對象,如訂單對象
里面包含了Customer對象,Spring需要你先自行創建了Customer對象并把它賦給了Order對象,才可能實現
order.customer.customer_no這樣的綁定。我偷懶,又拿Jakarta BeanUtils出來自己做了一個Binder。
3.5.提取基類
最后還是忍不住提取了一個基類,負責MultiAction和其他一些簡便的方法。Sprnig的MultiActionController做得太死,規定所有函數的第1,2個參數必須是request和response,不懂動態的,溫柔的進行參數注入。
經過化簡再化簡,已經是很簡單一個Java Bean ,任誰都可以輕松上手,即使某年某月技術的大潮把現在所有MVC框架都淹沒了,也不至于沒人識得維護。
IV-View層簡化
人生像個舞臺,請良家少女離開。
同樣的,Freemarker和Velocity愛好者請跳過本篇。與棄用webwork而單用Spring MVC
Controller接口的理由一樣,Freemarker本來是一樣好東西,還跨界支持jsp 的taglib,但為了它的非標準化,用戶數量與IDE的缺乏,在View層我們還是使用了保守但人人會用,IDE友好的JSP2.0
配合JSTL。
對于B/S結構的企業應用軟件來說,基本的頁面不外兩種,一種是填Form的,一種是DataGrid
數據列表管理的,再配合一些css, js, ajax的效果,就是View層要關注的東西了。
1. JSP
2.0的EL代替<c:out>
JSP2.0可以直接把EL寫在html部分,而不必動用<c:out>節點后,老實說,JSP2.0+JSTL達到的頁面效果,已不比Velocity相差多少了。
<p>{goods.name}</p>
代替
<p><c:out value="{goods.name}"/></p>
(除了EL里面不能調用goods的函數,sun那幫老頑固始終堅持JSTL只能用于數據顯示,不能進行數據操作,所以不能調用bean的get/set外的方法)
2.
最懶的form 數據綁定
Spring少得可憐的幾個tag基本上是雞肋,完全可以不要。 而Spring開發中的那些Simple Form
tag又還沒有發布。Spring的Tag主要用來把VO的值綁到input框上。但是,和Struts一樣,需要逐個Input框綁定,而且語法極度冗長,遇到select框還要自己進行處理.....典型的Spring
Sample頁面讓人一陣頭暈.
而jodd的form
tag給了我們懶人一個懶得多的方法,只要在<form>兩頭用<jodd:form
bean="myVO"></jodd:form>包住,里面的所有input框,select框,checkBox...統統自動被綁定了,這么簡單的事情,真不明白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有個致命弱點是不能綁定內嵌對象的值。比如Order(訂單)對象里有個Customer(顧客)對象,jodd就不能像
struts,spring一樣用如下語法綁定:
<input name="customer.customerNo">
這是因為它的beanUtils比Jakata Common弱,用了一個錯誤的思路的緣故。 動用beanUtils修改一下就可以了,修改后的源碼可以在這里下載。
3. DataGrid數據列表
DisplayTag和ValueList都屬于這種形式的Tag Library。但最近出現的Extreme
Table是真正的killer,他本身功能強大不說,而且從一開始就想著如何讓別人進行擴展重載,比如Extend
Attributes機制就是DisplayTag這樣的千人一面者不會預留。
4.css, javascript,
ajax
天下紛擾,沒有什么特別想講想推薦的,愛誰誰吧。