我在我們應該怎樣看待框架 中,實現了一個JDBC版的Base類,和一個Hibernate版的Base類,取模仿RoR的ActiveRecord,這樣我們編程的時候,只需要實現一個Base的子類,就可以完成CRUD了。
我的畢設題目是與Rest ,更準確說是與ROA有關的,另外就是在看Spring MVC對于慣例優先 (Convention over Configuration,CoC)原則的實現。
Web架構的四個元素
無論是Rails還是Spring MVC都是MVC實現,因此我的研究也肯定是基于MVC模式的。那么分別來關注M,V和C,另外就是將url和C進行映射的Router(或者說是Struts里的映射文件)。
首先是M,我不想自己編M,那就自動生成,怎么才能自動生成呢,根據數據庫自動生成,而且我生成要是一個ActiveRecord模式的類。所以不是像過去弄個HashMap就能混過去了。之后就是看了ASM3.1和ClassLoader的知識,發現這是可以實現的。如下:
先整個測試:
@Test publicvoidgerneratorClass(){ try{ Mapfields=newHashMap();fields.put("id",1l);fields.put("name","wanxing");EntityGernerator.generateEntity("Student",fields);EntityGernerator.invoke("add");}catch(Exceptione){ //TODOAuto-generatedcatchblock e.printStackTrace();} }public void gerneratorClass() { try { Map fields=new HashMap();fields.put("id", 1l);fields.put("name", "wanxing");EntityGernerator.generateEntity("Student",fields );EntityGernerator.invoke("add");} catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace();}這塊的實現我想了一下,EntityGernerator的generateEntity方法到底只是創建一個class還是連同實例化對象,而其還有invoke方法,這看上去不太對,嗯,它應該是只生成class就結束使命了。不過以后再迭代吧。
實現的代碼很簡單,就是利用了ASM3.1,就搞定了。有兩點需要聲明:
1、可以按需要設定生成類的父類
2、只能創建類屬性,不能創建方法,因為方法的實現太復雜,用JVM指令寫會死人,不如轉向動態語言了,而且它繼承了有用的父類,就已經完成了自己的使命了。
V和C都可以用通用的,就像用通用DAO一樣。關于V有個特別之處,那就是FrontController,它來調配各個Controller,依照URI和Controller的Mapping,這符合Roy Fielding博士對于資源的定義:資源是 一種概念上的映射 ——服務器接收到標識符(標識這個映射),將它應用于當前的映射實現(mapping implementation,通常是與特定集合相關的樹的深度遍歷和/或哈希表的組合)上,以發現當前負責處理該資源的處理器實現 ,然后處理器實現基于請求的內容選擇適當的動作+響應 .在RoR里這叫做Router.另外V中可以根據HTTP請求返回適當的Presentation.
REST與CoC
按照“慣例優先原則”可以做很多事情,比如Blog類對應BlogController,對應/blog.我們可以將該原則落實到以上四個元素上:M,V,C和Router.之后就是按照上述對應關系,形成一種簡單的開發框架,我們需要做的事情只是:
1、創建數據庫Schema,比如建立表Blog
2、配置數據庫連接,選擇使用何種數據庫
3、啟動Tomcat,在瀏覽器的地址欄中輸入http://localhost:8080/blog/new創建Blog(返回寫blog的表單),http://localhost:8080/blog/12,顯示文章;http://localhost:8080/blog/edit,返回修改表單;http://localhost:8080/blog/list,返回所有blog,加上q?time=2009-05-08或者http://localhost:8080/2009-05-08/blog/list,按時間查詢,后者的問題是如果Blog表有兩個字段是時間就沒辦法了,但是我們可以約定它只有一個時間的時候默認結果,這就是“慣例優先”作用。
輸入這些URL只是證明系統可以正常的工作。然后你可以定制你的HTML頁面,把這些鏈接放到你喜歡的地方,或者作為按鈕對應的服務。所有的服務,都是REST的。一個RCP客戶端也可以使用,或者是Delphi.
系統在背后默默的根據數據庫Schema創建了Blog類,然后其他都是通用的組件,放入Blog類,比如GenericController.setModel(T model),將Blog類的實例放入。
靈活性與可擴展性
如果只是這樣,那么這個系統實際上做不了什么太有價值的事情(這只是一個數據庫外的薄層),“慣例優先”并不是“慣例決定”,我們必須能讓Developer開發自己個性化的組件,以完成更強大的功能。
那么就要有如下邏輯:
1、對于M:首先檢查classpath里是否有url中尋找的實體(比如blog,我們不能說那是資源,資源是映射),這個實體就是Developer創建的,如果沒有,則去數據庫中查找名為blog的表,創建Blog類(并不在文件系統中生成這個class)。如果也沒有,則返回not found.
2、對于C:首先根據Router中的默認定義,尋找BlogController,檢查是否存在于classpath中,沒有則用ControllerGenerator生成BlogController(與EntityGenerator相同,不過既然我的Generator可以指定父類,實際上用一個ClassGenerator就應該OK了)。
3、對于V:現在默認路徑下尋找頁面(jsp或者html),如果沒有,則創建Blog頁面流(Stream)返回給客戶端。
4、對于Router:默認就是/blog對應BlogController.當然你可以建立自己的Router文件,來修改映射關系。
“慣例優先”,那這個慣例是誰的慣例呢? 其實Router的可Developer定制就表示了系統可以支持在四個Web架構元素中之間建立自己的“慣例”。比如,所有的表名都加上T_Blog,但是生成類名是Blog,而Controller是BlogAction(有人就是喜歡叫它Action),OK,這都沒問題。
技術細節
我們回到M討論。我這個M既要承擔ORM的責任又要承擔DVM(Domain View Mapping)的職責。也就是說,我期望我的對象里面可以關聯其他對象,同時我也希望我在UI上顯示正確的中文名,而不是字段名。
先說ORM,關聯關系(繼承就先別指望自動生成了),我可以根據數據庫表的主外鍵關系生成,然后用ASM動態加上注解(還是基于Hibernate)。我現在唯一不確定的是,用ASM生成的屬性,類型可以是另外的類嗎?答案是可以,跟內置的Java類型是一樣一樣的。
然后是我要讓生成HTML或者XML能正確顯示字段的Label,這個可以提取表的注釋,然后利用ASM動態加上注解。
ust Play
對于那些基礎數據的簡單維護,或者你的系統設計本來就不想什么OO范型,那么這個基礎框架是有用的,面對真正復雜的大型企業應用,它比較好的一點是不會阻止你實現復雜的對象圖。
寫到這,我第一次感到:我干嘛還要用Java呢?因此最后我只能說,Just Play,正如Dave所說,在真正的項目中,代碼生成并不像看起來那么有用,但是,起碼你有了一個好的腳手架~~~