作者: 江南白衣 像工匠一樣進行重構, 讓重構成為一門手藝.
Martin Fowler的《Refactoring》其實更適合做一本關于重構的洗腦,宣言式的書,就像Kent Beck的《XP Explain》一樣薄薄的就可以了。只可惜他卻非常的厚,后面的重構名錄都是寫給小白看的。所以我更喜歡《Refacoring WorkBook》,以一個工匠的語氣(沉默寡言而實要)傳授重構的手藝。
1.重構 Between Classes
〈Design pattern〉有半數篇幅教育大家不能只靠繼承,要善用組合/委托。重構里面其實也有很多事情靠把繼承變成委托來解決。
1.1繼承 1.1.1 并行繼承體系,組合爆炸 這在以前是個頭痛的問題,現在都已習慣用委托。 另外java還有個不是很讓人滿意的接口機制解決并行繼承。 1.1.2 父子類的關系
比如過于親密,子類會亂修改父類的方法,訪問父類的變量,這時候可以定義final的Template方法。
還有拒絕的饋贈,我暫時還沒有在這上面遇到問題,作者也建議如果沒事就由他,如果有事,就要費勁的move method ;或者子類不繼承父類,而只是組合父類。
1.2職責 經過很多次重構之后,我發現,其實哪個方法應該放在哪個類其實很主觀的,你每天醒來都能想到一個理由讓一個方法搬一下家,所以我現在已經放棄追求一種“對”的職責分配了,看著順眼就行。
1.3散彈式修改 作一個修改就要改N個類時,也沒什么特別好方法,就是找找看,有沒有能為這個修改負責的統管全局的類。 但現在的很多散彈式修改是分層做成的。
1.4庫類 OpenSource的類庫,總有些時候會想要擴展 1.如果只是一兩個方法,直接在客戶代碼里擴展, 2.否則自己多一個類庫的子類 3.最費勁就是引入一個新的層
題外話,重構其實很依賴工具,和對全部代碼的擁有度,嘩一下就來個全項目的rename。當你設計庫類時,你并不一定擁有使用這些庫類的客戶代碼了,因此一開始就要認真設計,不能依賴重構,改接口會讓人K死的。
2.重構 Within Classes
2.1 大是罪 Long Method、Large Class、Long Parameter List, 一般通過度量工具找出來,還可以自己設定一個觸發器,當度量值超過某個限度時就報警。 PMD可以實現這個功能,但度量工具我更喜歡Metrics Reload,一個IDEA的插件,給出的度量信息很全面。 但是也切忌為了度量的數值而重構。 Long Method當然是嘗試Extract Method。 Large Class就要把類的職能分開幾個域,嘗試拆出另一個Class或者SubClass。 Long Parameter List 可以通過在方法內的變量用查詢獲得而不用由參數傳入; 或者把某些參數組成一個對象。
1.2 重復也是罪 重復在30年前就被認為是不好的一樣東西,表現在1.代碼類似,2.代碼、接口不同而作用相近。 去除重復的方法也沒什么特別,無非就是 Extract Method(同一個類)。有差異時,通過參數化達到共用。 Pull Up Method到父類(同一個父類)。有差異時,通過模板機制達到共用。 Class A調用Class B 或者 Extract Class C (兩個完全無相干的類)
1.3 命名 中國程序員的英文本來就差,要多參考Ofbiz、Comperie的命名, 盡快建立團隊的項目字典、領域術語字典。
也幸虧,現在在工具輔助下,代碼的rename是最容易的重構。
1.4復雜條件表達式 作者認為,即使現在Program最關注的是對象,以及對象間的關系了,但優質的內部代碼依然重要,推薦《編程珠璣》和《Elements of Programing style》。 化簡復雜條件的基本有三個方法 1.通過!(A&B)==(!A)||(!B)化簡 2.通過有意義的變量,函數代替條件表達式,比如 boolean isMidScore = (X>1000)&&(X<3000); 3.通過把一個if拆分開來執行,把guard clause放在前面 比如if(A||B) do(); ->if(A) do(); if(B) do(); 又可以把2、3靈活組合,比如根據2,Extract出一個isRight()函數,根據3 isRight() { if(A) return true; if(B) return true; return false; } 1.5 其他 沒用的死代碼,通過IDE工具探知并移除。小心有些框架代碼是不能刪除的。 Magic Number,當然是改為常量。如果Magic Number很多,可以用Map、枚舉類來存放。 除臭劑式的注釋,為方法,變量改一個更適合的名字。如果注釋是針對一個代碼段的,可以Extract Method。當然,代碼只能說明how, 不能說明why,更不能說明why not,這才是注釋存在的地方。
Ilog JRules 是最有名的商用BRMS,剛拿了JOLT; Drools 是最活躍的開源規則引擎,一路高歌猛進; Jess 是Clips的java實現,就如JRuby之于Ruby,是AI系的代表。
今天對比了一下這三個頗有代表性的規則引擎的規則語言。其中Ilog是商業產品,沒有機會實戰。
作者:江南白衣
1.一樣的If--Then 句式與Rete引擎
三者都會把原來混亂不堪的if---else---elseif----else謎團, 拆成N條帶優先級的"If 條件語句 then 執行語句" 的句式。 三者都主要使用foreward-chaining的Rete引擎,按優先級匹配條件語句,執行規則語句。 規則執行后會引發事實的變化,引擎又會重新進行條件匹配,直到不能再匹配為止,Rete的算法保證了效率的最高。
2.開發人員使用的規則語言
2.1 Drools的XML框架+Java/Groovy/Python嵌入語言
Drools的用XML的<Conditons>、<Consequence> 節點表達If--Then句式,而里面可以嵌入上述語言的代碼作為判斷語句和執行語句。 其中Java代碼會使用Antlr進行解釋,而Groovy和Python本身就是腳本語言,可以直接調用。 Drools的聰明之處在于,用XML節點來規范If--Then句式和事實的定義,使引擎干起活來很舒服。 而使用Java,Groovy等原生語言來做判斷和執行語句,讓程序員很容易過渡、移植,學習曲線很低。
<java:condition> hello.equals("Hello") </java:condition>
<java:consequence> helloWorld( hello ); </java:consequence>
2.2 ILog的IRL(ILog Rule Language)
IRL用When{}Then{}表達 If--Then句式
When { ?customer: Customer(totalTime >=1000); } Then { execute {?customer.setAmount(getAmount()-20.00); }
文檔稱IRL的語法是Java Syntax-like的,但我怎么也看不出兩者是相同的。不過他因為是商業產品,有很強大的編輯器和管理工具,編寫規則的速度應該不壞。
2.3 Jess的CLIPS jess用 => 表達 If-Then句式。 這CLIPS是真正的程序員專用語言,而且還要是很專業的程序員才習慣的東西。但這種本來就是用來做專家系統的AI語言,對規則的表達能力也應該是最強的。 講解一下下面這段代碼,airplane有一個屬性--name,有兩個子類--噴氣式和螺旋槳飛機,其中螺旋槳飛機可以使用任意跑道,而噴氣式飛機不能使用Grass跑道。
; Fact templates (deftemplate airplane (slot name)) (deftemplate jet extends airplane) (deftemplate prop extends airplane) ;
Rules (defrule can-use-grass-runway (prop (name ?n)) => (printout t "Aircraft can use grass - " ?n crlf)) (defrule can-use-asphalt-runway (airplane (name ?n)) => (printout t "Aircraft can use asphalt - " ?n crlf)) |
3.客戶使用的規則語言
如果客戶可以自己任意編寫規則,無疑是產品一個很大的賣點。大部分客戶都會喜歡這樣一個玩具。而且也只有把規則編寫交給客戶,才能達到規則引擎的全部意義。
3.1 Drools的 DSL Drools的最新版Drools2.0Rc2里,House和Conways game of Live兩個例子有DSL的版本 對比一下Java版本,效果如下:
<house:condition> <house:room name="calvin"> <house:溫度> <house:greater-than scale="攝氏">20</house:greater-than> </house:溫度> </house:room> </house:condition>
vs
<java:condition> room.getName( ).equals( "calvin" ) <java:condition> <java:condition> convertToCelsius( room.getTemperature() ) > 20 <java:condition>
但這種XML Base的DSL語法其實好不了多少,而且實現的代價一點不少,要自己實現Conditons和Consequence Factory類,自行解釋那段XML,基本上沒有什么便利的底層支持。 其實,一不做二不休,干脆用Antlr來定義真正的DSL,同樣是實現Conditons和Consequence Factory類可能更好。只不過解釋XML人人都會,Antlr就比較少人用而已。
3.2 ILog的BAL(Business Action Language)--最完美的王者? 沒有實際用過,只能看文檔過過癮。從文檔來看,配合Ilog的編輯器,的確就是很完美的規則語言了。
If the call destination number is the preferred number Then apply the preferred number rate
JetBrains的MPS出來了,Martin Fowler也大力搗鼓出一篇《Language Workbenches: The Killer-App for Domain Specific Languages?》,成為有志于LOP、DSL領域的總領性文章。
首先,了解Martin Fowler的立場很重要。但似乎為了保證閱讀率,MF把立場擺到了最后。
1. LOP帶來的兩個最大優點是 a. 通過新的封裝及思維模式,提高程序員的生產率。 b. 改變程序員與領域專家的關系,最理想情況是領域專家直接用DSL編程。 MF認為第2點比第1點帶來的效果大得多,但也困難得多。COBOL剛出來的時候已經有人提領域專家直接編程了,結果呢? 2.現在大家對DSL應該是什么樣子的還知之甚少,文本語言?圖形語言?一切都還在假設。 3.現在的LOP工具還在非常初始的階段。
4.但MF同時認為LOP是目前最有趣的想法,不論它日后成不成功,都會激發出大量有趣的思想與實踐,留意一下LOP是絕不會吃虧的事情。
是不是熱情驟減?本來MPS的發布使LOP看起來像是明天就可以開始動手的事情,現在又變成了雖然很有趣,但還遠沒到下山摘果子時候。 從頭讀一遍文章 1.開頭 A Simple example of LOP Martin舉的這個例子占了全文1/3的篇幅,又長又不刺激神經,看得大家頻頻起身吃零食,上廁所.... 2.傳統的LOP MDA不是什么新概念,DSL當然也用不著是,DSL其實早就在我們身邊,包括 1.Unix下用yacc打造的微型DSL 2.Lisp,fp用自身來構造DSL 3.XML配置文件 4.GUI描述文件(VB, Delphi....) 5.Adaptive Object Models and Active Data Models? (沒完全理解) (注:SQL也算吧)
3.External DSL和Internal DSL DSL分內外兩種,像yacc這種把DSL parser后translate成base語言的屬于External DSL。 而Lisp這種用語言本身來構造新的語言的稱為Internal DSL。
External DSL的好處是它可以是任何樣子的,不受Base語言的制約。另外它也通常是運行時解釋的。 不好的地方: 第一, 它需要花很多時間去設計語言,寫Parser,寫Generator,寫IDE。 第二, 不能直接使用Base語言的IDE,在后IntelliJ時代這讓人很不爽. 第三, 需要學太多語言和思維方式,不是指if-else語法的不同,而是在java里我們已經習慣了用Object和Method來表達想法,但在其他DSL里則可能要運用完全不同的概念,比如文章開頭的例子。
而Internal DSL和External DSL的優缺點很多地方正好調轉。而且Lisp,Smalltalk的語法和我們平常的Java,C#差別很大。還有,最近Ruby們好像也有可能用來寫Internal DSL了。
正是因為兩種DSL都缺點明確,所以DSL在今天這么不普及。Language workbeanch,正是為了使External DSL變得容易而出現的。
4.今天的Language Workbeanch 有Intentional Software的IP, JetBrains的MPS和微軟的軟件工廠。
1.一段DSL將有一個Editble reprensentation,一個storage reprentsentation,一個編譯后的excuteble reprentsentation,一個在editor中的AST-Astraction reprensentation。其中editble和storage reprensentation可以合一,也可以分開。 2.定義一個新DSL的三個步驟: a.定義語言的schema b.定義編輯器 c.定義Generator 一個DSL可以擁有多種編輯器和代碼生成器。 5.Language WorkBench的優缺點 優點: 1.省卻了寫Parser,直接定義抽象語法。 2.省卻了寫IDE。 3.IDE的語法提示與語法檢查,給領域專家直接編寫提供了可能,這是COBOL時代沒有的。 4.DSL與項目的良好集成,可以項目與DSL語法一起refactor,可以一邊設計語言一邊使用語言。
缺點: 1.Vendor專屬,用了MPS,就不可能再轉到IP或者微軟,因為他們之間根本沒有標準可言。 2.但Generator并沒有比以前簡單(要命阿)。 3.現在代碼以astraction reprensention為中心,版本管理,AST支持diff/merge的問題。
6.我的立場 試用了一下MPS,因為Generator還沒有革命性的突破,MPS還沒到真正可用的時候。 不過幾個月間,MPS EAP已經從初始的150版本升級到220,讓人無法忽略它的進度。
作者:江南白衣
注重實效的TDD的確能加快,而不是拖慢開發的進度(片面的追求覆蓋率的全面UnitTest不在此列) 一,可以實現真正分層開發。 二,不需要依賴和頻繁重啟Web Container。 三,手工測試總不免改動數據庫,如何把數據庫恢復到測試前的狀態是件傷腦筋的事情。而Unit Test可以使用自動Rollback機制,巧妙的解決了這件事情。
Spring 下的Unit Test主要關注三個方面: 1. bean的依賴注入 2. 事務控制,Open Session in Test 及默認回滾 3. 脫離WebContainer對控制層的測試
1.bean的依賴注入 能不依靠WebContainer來完成ApplicationContext的建立與POJO的依賴注入一向是Spring的得意之處。
String[] paths = { "classpath:applicationContext*.xml" }; ApplicationContext ctx =new ClassPathXmlApplicationContext(paths); UserDAO dao = (UserDAO) ctx.getBean("userDAO");
如果你連這也覺得麻煩,那么只要你的testCase繼承于Spring-mock.jar里的AbstractDependencyInjectionSpringContextTests,實現public String[] getConfigLocations()函數, 并顯式寫一些需要注入的變量的setter函數。 注:因為是AutoWire的,變量名必須等于Spring context文件里bean的id。
2.Open Session in Test 及自動Rollback 又是來自Spring這個神奇國度的東西,加入下面幾句,就可以做到Open Session in Test ,解決Hibernate的lazy-load問題;而且接管原來的DAO里的事務控制定義,隨意定義測試結束時是提交還是回滾,如果默認為回滾,則測試產生數據變動不會影響數據庫內數據。 你可以讓testCase繼承于AbstractTransactionalDataSourceSpringContextTests,通過setDefaultRollback(boolean)方法控制最后回滾還是提交。 如果自己編寫,代碼是這樣的:
protected PlatformTransactionManager transactionManager; protected TransactionStatus transactionStatus; protected boolean defaultRollback = true; public void setUp() { transactionManager = (PlatformTransactionManager) ctx.getBean("transactionManager"); transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition()); } public void tearDown() { if (defaultRollback) transactionManager.rollback(this.transactionStatus); else transactionManager.commit(this.transactionStatus); }
(注,hibernate太奸詐了,如果全部默認回滾,只會在session里干活,一點不寫數據庫,達不到完全的測試效果。)
3.Controller層的Unit Test
controller層靠Spring提供的MockHttpServletRequest和Response來模擬真實的servlet環境,并且spring 2.0了加了一個AbstractModelAndViewTests,提供一些檢測返回值的utils函數。
protected XmlWebApplicationContext ctx; protected MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); protected MockHttpServletResponse response = new MockHttpServletResponse(); protected Controller controller = null; protected ModelAndView mv = null; public void setUp() { String[] paths = {"applicationContext*.xml","myappfuse-servlet.xml"}; ctx = new XmlWebApplicationContext(); ctx.setConfigLocations(paths); ctx.setServletContext(new MockServletContext("")); ctx.refresh(); controller = (CustomerController) ctx.getBean("customerController"); // 再加上前文的事務控制的代碼 } public void testCustomerList() throws Exception { request.setRequestURI("/customer.do"); request.addParameter("action", "listView"); mv = controller.handleRequest(request, response); assertModelAttributeAvailable(mv, "customers"); }
4.進一步簡化 一來這兩個基類的名字都太長了。 二來有一些公共的context文件的定義。
所以可以再抽象了幾個基類,分別是DAOTestCase,ControllerTestCase。
5. EasyMock MockObject是一樣徹底分層開發的好東西,而且使用上沒什么難度。而且已不再存在只支持接口不支持Class的限制。
//設定BookManager MockObject bookManagerMockControl = MockClassControl.createControl(BookManager.class); bookManagerMock = (BookManager) bookManagerMockControl.getMock(); controller.setBookManager(bookManagerMock); //錄制getAllBook()和getCategorys方法的期望值 bookManagerMock.getAllBook(); bookManagerMockControl.setReturnValue(new ArrayList()); bookManagerMockControl.replay(); //執行操作 mv = controller.handleRequest(request, response); //驗證結果 assertModelAttributeAvailable(mv, "books");
Easy Mock VS JMock:
JMock 要求TestCase繼承于MockObjectTestCase太霸道了。妨礙了我繼承于Spring2.0的ModelAndViewTestCase和使用MockDao,RealDao并行的繼承體系。因此采用沒那么霸道的easyMock。
另外,easyMock的腳本錄制雖不如jmock那么優美,但勝在簡短易讀。jmock那句太長了 。
6. 顯示層測試 還有,顯示層至今沒有什么好的UnitTest方法,無論是不成才的httpUnit們還是笨重的GUI test工具。Appfuse一直用的那個ThoughtWork那個Selenium和J3Unit的效果不知如何, 其中J3Unit號稱支持prototype。
作者: 江南白衣 之前用Groovy的嵌入式xml語法時, 發現遍歷DOM只有children()函數而沒有parent(),就幫它修改了一下,連UnitTest一起提交到Groovy的JIRA上。 今天看到開發組接納了我的改動。 > [patch] add parent() implement to XmlSlurper > Key: GROOVY-1010 > The XmlSlurper only have the children() method to explore DOM. > But in our project, it's so offten to use parent(). > i had patch it with unit test . John Wilson resolved GROOVY-1010: ------------------------------ ---
Resolution: Fixed
Patch added - thanks Calvin!
現在很多開源項目都有了Issue Tracker的渠道,方便大家參與測試和修改。 大家在消費別人的開源努力時,也應該習慣把自己改過的東西提交回去。
作者:江南白衣
以Spring為代表的提供依賴注入的IOC Container風頭越盛,比起IOC的原本意義,DI逐漸有妹仔大過主人婆的姿勢,所以Martin Fowler同學忍不住寫了篇blog,提醒一下大家IOC的本原--一種作為"所有Framework與API Library最根本的區別點"的Design Principle。 當年侯捷同志是以VC下的MFC作例子,馬同學與時俱進,換了Ruby、Junit、SWT來教育時下的新新人類。 IOC原理是老生常談了,可以看馬同學的blog。當應用復雜時,都應該考慮把封裝從線性調用的API級,提升到奉行IOC的Framework級。
我更關心如何把自己的代碼交給IOC框架調用。 1.閉包,匿名內部類,函數指針 如果僅僅把一段代碼plug給框架,Groovy、Ruby的閉包是最簡單快捷的,再看Java中匿名內部類的做法,比如SWT的事件綁定,Spring的JDBC Template,代碼之難看令人心酸,很無妄的的要多一個接口名,一個方法名和兩層嵌套,見Martin Fowler的<閉包>的中文版.
2.Template模式,策略模式 如果要把一組關聯的代碼綁定給Framework,則通常會定義出一個接口。這里的接口是廣義的。GOF里有兩種模式: 一種是模版(Template)模式,幾種最簡單、最古老的模式之一。在父類里面定義Control flow,留下鉤子程序的位置。而在子類里實現這些鉤子程序,Junit的setup()和tearDown()就屬于這種類型。
另一種是策略模式。Template的一個重要制約為兩者必須是父子關系,這對于單根繼承的java有點不便。另外,策略模式在靈活性上顯然更勝一籌。策略類只需要實現某個接口,容器就可以在適當時進行調度。比如EJB,和Swing都是這種模式。
3.消息綁定 最后,MS家還有個更高明的消息機制,可以實現更靈活的綁定。
4.AOP, cglib和Annotaton 另外,馬同學沒有講的,因為AOP和元數據的出現,框架本身又有了新的封裝方式。 框架可以用AOP更隱式,無侵入的提供服務,Annotation可以更簡潔的告訴框架如何提供服務。
比如Spring 的JDBC Framework ,封裝了連接,事務,異常的管理,讓你可以專心的寫SQL查詢。但那些個匿名內部類讓你怎么看怎么不爽,而Java又的確沒有閉包......這時你可以用Spring的聲明式事務管理機制,你只要把業務代碼寫成普通函數,而Spring會利用AOP隱式的包裹你的代碼提供連接、事務的管理。
如果你不喜歡用xml配置文件聲明事務,可以自己用cglib+annotation簡單實現一下。甚至,如果你連 annotation也不喜歡,還可以學習rails, 用純命名約定搞定,只在必要時采用annotation輔助。
潮流興用Ruby寫Sample Code. 還有一樣事情,就是馬同學開始喜歡用Ruby來寫sample code。發現Ruby寫sample code的確好,讀的時候像偽代碼一樣清晰簡單,寫的時候也可以一氣呵成,沒有太多無聊定義與制約,也不用怕別人投訴代碼編譯不了....
作者: 江南白衣 一個Appfuse式的項目,會通過項目里最典型的幾個場景,demo團隊目前的體系框架和設計模式。
它的好處有一打,比如為所有項目提供共同的Library Stack,提供最可靠的代碼藍本,保證大家的模式和代碼風格一致,加快知識在團隊的傳播,方便新人的融入,還有為試驗代碼提供一個穩定簡潔的環境。
所以,一個長期合作的團隊,需要這樣一個MyAppfuse。
但還要有三條鐵的紀律,才能保證辛苦做出來的MyAppFuse不是個寂寞的芭比。 一是強制更新,所有團隊approval的最新模式都要refactor到MyAppfuse中。 二是規范更新,每次更新都要嚴格測試并編寫更新記錄、移植文檔。 三是強制Copy Start,所有代碼都必須從MyAppFuse里Copy而不是隨自己喜歡找任意項目的代碼。
現在開始規劃一個Appfuse式項目。我覺得包含如下Content: 1.設計典型的應用情景。 我平時的ERP項目,最典型的情景莫過于: *基礎資料管理(如產品資料的CRUD) *單據管理(如訂單的錄入與管理) *典型報表
每個場景應該有簡單與復雜兩種模式,方便Developer選用。 場景要仔細設計,盡量演示到所有重要的技術要點。 但場景又要盡量的少,盡量簡潔,減少每次模式升級的成本。
2.挑選出其他比較重要的特性。 如Quartz、ClickStream,也一并放入MyAppFuse中。
3.把所有用到的框架、類庫、瓶瓶罐罐統統打包。 并附上索引和說明作為團隊公用的Library Stack,每次library升級都要認真檢測。
4.編寫文檔。 類似Appfuse的Tutorial,編寫文檔說明各個場景用到的技術要點與模式,說明如何二次開發。 類似Appfuse的Migrate,詳細說明如何升級到MyAppfuse新的版本,促進新模式的傳播。
5.簡單代碼生成工具。 類似Appfuse的AppGen,用Groovy Template或FreeMarker編寫簡單的代碼生成模版。
6.核心的測試用例
后記:這個MyAppfuse終于開源成http://www.springside.org.cn
作者: 江南白衣 擴展Spring系列(1)--Spring 的微內核與FactoryBean擴展機制DreamHead在 《思考微內核》十分激賞 Spring的微內核與擴展機制:
“Spring的微內核在哪里呢?便是DI容器。而通過FactoryBean,我們可以定制自己的組件組裝過程,對一個普通的JavaBean做手腳,像Spring AOP中常用的ProxyFactoryBean做的那樣。如此,我們就不必把所有功能都做到Spring的DI容器中去,而是以一個FactoryBean來對DI容器的功能進行擴展。除了Spring自身之外,現在已經有一些項目開始利用這個特性擴展Spring,比如,Acegi Security和Spring Modules。” 這確是框架容器界應該貫徹的范式,微內核提供最少的功能,而由擴展接口去增強框架的能力。下面看看Spring怎么設計,明白之后就可以開始為Spring捐獻精力了:) 1、微內核的功能1.1 DI(依賴注入)與Singleton管理 利用POJO setter的DI機制,估計每位同學隨手都能寫一個簡單版本,不多說了。 Singleton管理說白了就是先到一個map中按id找找看有沒有已存在的實例。 1.2 BeanName與BeanFactory注入 除了DI注入的屬性,微內核還有什么能賣給POJO呢?就是Bean在xml 定義里的id和BeanFactory自己了。 賣的機制是讓POJO 實現 BeanNameAware和BeanFactoryAware接口。BeanFactory用 if(pojo instance of BeanFactoryAware)判斷到POJO需要注入BeanFactory,就調用setBeanFactory(this)將自己注入。 這種框架中 基于接口的注入和調用機制在Java下挺標準的,Spring的功能多是基于這種模式提供。遺憾就是Java不支持多重繼承,作為替代的接口里不能提供默認的實現,導致每一個Pojo都要很無聊的實現一遍setBeanFactory()。 1.3 DI后的初始化函數調用 比如屬性A,B注入之后,需要同時根據A和B來對A,B進行加工或者裝配一個內部屬性C,這樣就需要在所有屬性注入后再跑一個init()函數。 Spring提供兩種方式,一種是和上面的原理一樣,實現InitializingBean接口的afterPropertiesSet()函數供Spring調用。 一種是在xml定義文件里面自行定義init函數名。 懶得每次在xml文件里定義的就采用第1種方式,不想與spring耦合的pojo就采用第2種方式。本來就是為了擴展Spring而存在的FactoryBean多采用第一種。 所謂微內核,就是僅提供以上三種功能的DI容器。 但作為輕量級容器,還需要以下兩種方式,向容器內的POJO 附加各種服務。2.FactoryBean擴展機制Spring的AOP、ORM、事務管理、JMX、Quartz、Remoting、Freemarker、Velocity,都靠FacotryBean的擴展,FacotryBean幾乎遍布地上:
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"/>

<bean id="baseDAOService"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"/> 只不過當年對這類factoryBean比較麻木不仁,不問原理的照搬照用了。 不過這原理說出來也好簡單,所有FactoryBean 實現FactoryBean接口的getObject()函數。Spring容器getBean(id)時見到bean的定義是普通class時,就會構造該class的實例來獲得bean,而如果發現是FacotryBean接口的實例時,就通過調用它的getObject()函數來獲得bean,僅此而以.......可見,很重要的思想,可以用很簡單的設計來實現。 考察一個典型的FactoryBean:
一般會有兩個變量,三個接口: 一個setter函數注入需要改裝的pojo,一個內部變量保持裝配后的對象returnOjbect。 implements三個接口 :FactoryBean,InitializingBean和BeanFactoryAware 。 各接口的意義之前都講過了。factoryBean會在afterPropertiesSet()里把pojo改裝成returnObject,需要用到beanfactory進行天馬行空的動作時就靠BeanFactoryAware注入。最后在getObject()里把returnObject返回。 Rod說:IoC principles, combined with the factory bean, afford a powerful means to abstract the act of obtaining or accessing services and resources 3. Bean Post-Processor擴展機制 如果說FactoryBean 是一種Factory、Wrapper式的擴展,Bean Post-Processor就是另一種AOP、visitor式的機制,所以也多用于spring的AOP架構。 Post-Processor的原理就是BeanFactory在前文里的調用afterPropertiesSet()/init-method前后,調用在工廠里注冊了的post-processor的postProcessBeforeInitialization()和postProcessAfterInitialization()。 那怎么注冊登記呢?又分請不請禮儀公司兩類。如果是ApplicationContext,你把繼承BeanPostProcessor 的bean往xml里一擱就行了,application context自會打理。如果是BeanFacotry,就要顯式的注冊,代碼大概像:
XmlBeanFactory factory = new XmlBeanFactory("C:/beans.xml"); BeanPostLogger logger = new BeanPostLogger(); factory.addBeanPostProcessor(logger); Rod說:"Post-processors add the ability to customize bean and container behavior in a flexible, externalized fashion. " 對比Factory Bean那段,可見兩種機制在他心目中的不同作用。 系列文章:Spring 的微內核與FactoryBean擴展機制 擴展Spring(2)--Spring對各種數據訪問框架的集成機制
作者:江南白衣
ANTLR(ANother Tool for Language Recognition)風頭正盛,經常可以看到用它做語法解釋器的項目,比如Hibernate就在3.0換上它來解釋HQL,加強了HQL的語法。 因為Antlr是EBNF-AST語法解釋系的代表,而自己總是心思思想搞一下DSL(領域語言),所以從Hibernate來學習一下Antlr的應用。
Hibernate HQL translator作者Joshua Davis的兩個Blog Hibernate3 Query Translator Design - Part One : The Basics Hibernate3 Query Translator Design - Part Two : Parsing HQL
Antlr最好的介紹文章是那篇,在《程序員》2004年3月有中文的版本。 不過,那個計算器的例子太簡單了。深刻一點的有。 另外,SlickEdit 支持Antlr的語法,是一定要用的編輯器,在 ttdown.com上有破解。
一,Antlr引擎的工作過程大概是這樣的: 1.Lexer類--詞法分析器。 定義語言中的各種Token(單詞),如 From 、Where、=、<>....... Lexer負責把讀入的普通文本流識別成Token串。
2.Parser類--語法分析器。 使用BNF語法,遞歸定義句子的Pattern,如whereStatement、FromStatement、SelectStatement。 Parser負責把讀入的Token串匹配成句子,翻譯出AST(抽象語法樹)。 有些簡單的應用,也可以在本層現炒現賣,完成所有動作,屬于Single Pass Builder。
3.TreeParser類--抽象語法樹遍歷器。 根據Parser類分析出來的AST(抽象語法樹)進行動作。 用Parser把AST抽取出來,再用TreeParser進行動作的Double Pass Builder模式,解耦了Parser和Generation,再配合Template 生成代碼,是Antlr推薦的最佳模式。
二,開發人員的實際步驟
1.按照Antlr的簡單語法定義前面講的3個類,文件的后綴名為g。
2.使用java antlr.Tool xxx.g 命令,把grammar文件編譯成java文件。
3.編寫應用程序,如:
import antlr.*; import antlr.collections.*; public class Main { public static void main(String[] args) throws Exception { ExprLexer lexer = new ExprLexer(System.in); ExprParser parser = new ExprParser(lexer); parser.expr(); AST ast = parser.getAST(); ExprTreeParser treeParser = new ExprTreeParser(); int x = treeParser.expr(ast); } }
三,Hibernate對Antlr的應用 看過Antlr對HQL的解釋,覺得EBNF系的方法要解釋Java這樣的編程語言還好些,如果要解釋類自然語言的DSL就比較痛苦,所以情緒不是很高漲,挑一條最容易的"Delete from goods where ....." 匆匆走過場
Joel的一句話對我的影響比較大:"如果為了證明一個微不足道的問題需要花三個小時寫下幾黑板的證明步驟,那么這種機制不可能用來證明任何有趣的東西" 。對于我這個層次的程序員,antlr在我手中造不出有趣的DSL來。
Hibernate的HQL Grammar文件一共有三個,在/grammar目錄下: 1.hql.g 定義Token類和Parser類,將HQL解釋成hql的抽象語法樹(AST) 2.hql-sql.g 定義Tree Walker ,將HQL AST轉化為SQL AST,將生成模塊與Hibernate解耦。 3.sql-gen.g 定義Tree Walker,從SQL AST生成sql
下面看 DELETE FROM GOODS的翻譯過程
1.HqlBaseLexer extends Lexer 定義EQ: '='; LT: '<'; GT: '>';PLUS: '+';等符號 及IDENT: ( 'a' .. 'z' | '_' ) ( 'a' .. 'z' | '0' .. '9' | '_' | '$' )*
2.HqlBaseParser extends Parser 先定義DELETE="delete"; FROM="from"; MIN="min"; 等字符串 再定義:
statement : ( updateStatement | deleteStatement | selectStatement ) ; 三種Statement之一
deleteStatement : DELETE^ (optionalFromTokenFromClause) (whereClause)? ;DELETE為葉子,(whereClause)可選
optionalFromTokenFromClause! : (FROM!)? f:path { AST #range = #([RANGE, "RANGE"], #f); #optionalFromTokenFromClause = #([FROM, "FROM"], #range); } ;不是很好懂對吧,我也這樣覺得,whereClause就更加不要看了。
3. HqlSqlBaseWalker extends TreeParser hql與sql的delete語句基本上是一樣的,沒什么轉換。
4.SqlGeneratorBase extends TreeParser 根據SQL AST, 生成SQL語句
private StringBuffer buf = new StringBuffer(); protected void out(String s) { buf.append(s); }
statement : selectStatement | updateStatement | deleteStatement ; deleteStatement : #(DELETE { out("delete"); } from (whereClause)? ) ;輸出"delete" from : #(f:FROM { out(" from "); } (fromTable)* ) fromTable : #( a:FROM_FRAGMENT { out(a); } (tableJoin [ a ])* { fromFragmentSeparator(a); } ) | #( b:JOIN_FRAGMENT { out(b); } (tableJoin [ b ])* { fromFragmentSeparator(b); } ) ; tableJoin [ AST parent ] : #( c:JOIN_FRAGMENT { out(" "); out(c); } (tableJoin [ c ] )* ) | #( d:FROM_FRAGMENT { nestedFromFragment(d,parent); } (tableJoin [ d ] )* ) ;
    .暈了吧 ~~~~~ whereClause : #(WHERE { out(" where "); } ( conditionList | booleanExpr[ false ] ) ) ;
擴展Spring(2) ---Spring對各種數據訪問框架的集成機制 何為數據框架集成。
數據訪問框架原本好好的,Spring都干了什么呢? 一是用template類封裝了數據框架那些資源獲取和異常事務處理的廢話代碼,而且按照自己的意見給出一些增強函數。 二是將其納入了Spring的聲明式事務管理中。 對比Spring對Hibernate、JDBC的集成,還有 Spring Modules對 O/R Broker的集成,發現Spring的DAO框架主要有六個類: 1.Template 著名的Template類,用callback機制封裝了除業務代碼外的所有必要但廢話的代碼,重新封裝了數據框架的API,并再附送一些增強版。 2.TransactionManager
實現PlatformTransactionManager接口,數據訪問框架就能與Spring的事務機制(TransactionTemplate或AOP聲明式事務)結合。 重要的類僅以上兩個,以下的類都只有少量標準代碼,完全可以忽略。 3.DAOSupport
實際DAO類的基類,負責保持template變量。如果你覺得它破壞了你的類層次結構,完全可以不用。 4.Accessor
template類的基類,defining common properties like DataSource and exception translator,也沒大用。 5.Operations
template所實現的接口,定義template支持的數據訪問函數和增強函數,template有多個實現時才有用。 6.Exception Translate的相關類和函數
異常翻譯,Spring DAO很重視的一個功能。 Template類的代碼
因為Hibernate本身很復雜,所以HibernateTemplate也不適合畏高暈車的人士如我觀看。JDBC簡單很多,但JDBCTemplate又忙著增強JDBC的功能,多出好多代碼。所以我選O/R broker的集成代碼來看,代碼一共才280行。 注:如果不熟O/R broker,可以簡單的認為broker=connection, executable = statement ,其余一切同Jdbc。 1.1主干函數 Execute(BrokerCallback action) step1. 獲得Connection-- connecton = datasource.getConn(); step2. 準備Statement -- statement = new Statement(connection); step3. 執行Action的回調函數doInBroker(Statement)。這個doInBroker()方法由客戶定義,會拿著傳入的statement,執行種種操作。
try { action.doInBroker(statement ); } catch( ) { // 翻譯異常 }
1.2 template的API函數 雖然理論上大家可以直接使用execute(),在匿名內部類里調用數據訪問框架的任何API。但java的匿名內部類不比閉包,代碼難看無比,所以除了Robbin還沒見到其他兄弟提倡直接用execute方法的。 因此,template也對數據框架的API進行了wrap,封裝了用execute(StatementCallback action)來執行這些API的函數,如下段就是wrap 了O/R Broker的execute(String statementID.....)方法:
public int execute(final String statementID, final String[] paramNames, final Object[] values) throws DataAccessException { return executeWithIntResult(new BrokerCallback() { public Object doInBroker(Executable executable) throws BrokerException { applyNamedParamsToExecutable(executable, paramNames, values); return new Integer(executable.execute(statementID)); } }); } 另外還提供一些增強型、便利型的API(如selectOne() ,selectMany()),在參數、返回值上極盡變化。 TransactionManager的代碼
比較復雜,一下說不清。但JDBC的DatasourceTransactionManager和Hibernate的HibernateTransactionManager的代碼都很相近,說明這個TransactionManager其實也比較固定埋頭狂抄就是了。 有興趣的同學,可以響應某大老號召,實現ofbiz與spring的集成:) 系列文章: Spring 的微內核與FactoryBean擴展機制 擴展Spring(2)--Spring對各種數據訪問框架的集成機制
作者: 江南白衣 人生像個舞臺,請良家少女離開。 同樣的,Freemarker和Velocity愛好者請跳過本篇。與棄用webwork而單用Spring MVC Controller接口的理由一樣, Freemarker本來是一樣好東西,還跨界支持jsp 的taglib,而且得到了WebWork的全力支持,但為了它的非標準化,用戶數量與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 天下紛擾,沒有什么特別想講想推薦的,愛誰誰吧。Buffalo, DWR, Scriptaculous, Prototype, AjaxTags, AjaxAnywhere, Rico, Dojo, JSON-RPC,看著名字就頭痛。
相關文章 簡化Spring(1)--配置文件 簡化Spring(2)--Model層 簡化Spring(3)--Controller層 簡化Spring(4)--View層
作者: 江南白衣 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到響應函數的映射。
我的取舍很簡單,反正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.提取基類
作者: 江南白衣 因為Spring自帶的sample離我們的實際項目很遠,所以官方一點的model層模式展現就靠Appfuse了。 但Appfuse的model層總共有一個DAO接口、一個DAOImpl類、一個Service接口、一個ServiceImpl類、一個DataObject.....大概只有受慣了虐待的人才會欣然接受吧。 另外,Domain-Driven逢初一、十五也會被拿出來討論一遍。 其實無論什么模式,都不過是一種人為的劃分、抽象和封裝。只要在團隊里理解一致,自我感覺優雅就行了。 我的建議是,一開始DO和Manager一生一旦包演全場,DO作為純數據載體,而Manager類放置商業方法,用getHibernateTemplate()直接訪問數據庫,不強制基于接口編程。當某天系統復雜到你直覺上需要將DAO層和Service層分開時,再分開就好了。 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層來為Dao解耦
在JPetStore里有一個很薄的Service層,Fascade了一堆DAO類,把這些DAO類的所有方法都僵硬的重復了一遍。理論上一個Manager類可以管理數個Dao類,可以避免Dao之間直接耦合。但既然有Manager的情況下,商業邏輯都是寫在Manager類的,那樣子Manager似乎還是調用另一個Manager比較妥當,調用裸Dao可能存在忽略了某些邏輯。所以,耦合又從Dao層升到Service層了。 所以,除非你做的是超薄的不帶邏輯的Service層,否則沒有解耦的意義。 何況,對一個不是死搬書的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,對我來說已經足夠復雜,再要往上架墻疊屋,恕不奉陪,起碼在我的項目范圍里不需要。(但有很多項目是需要的,神佑世人) 后記:迫于世人的壓力, SpringSide暫時還是把DAO和Service層分開了,但依然堅持不搞那么多接口。 另外,盡量利用IDEA的代碼生成熱鍵,為Manager類生成delegate的Dao類方法。 相關文章 簡化Spring(1)--配置文件 簡化Spring(2)--Model層 簡化Spring(3)--Controller層 簡化Spring(4)--View層
作者:江南白衣 序
人人都愛Spring加Hibernate。 但Spring MVC+hibernate的Sample如Appfuse的代碼卻算不得最簡潔優美好讀,如果在自己的項目中繼續發揮我們最擅長的依樣畫葫蘆大法,美好愿望未必會實現。 所以,Pramatic精神不滅。這個系列就是探尋最適合自己的Spring+Hibernate模式。 I-配置文件簡化
我厭倦一切配置文件繁重的框架。 最好的情況是,框架提供極端靈活復雜的配置方式,但只在你需要的時候。 Spring提供了三種可能來簡化XML。隨著國內用戶水平的提高,這些基本的簡化技巧大家都已掌握。 大家可以直接看第3,第4點--Spring 1.2, Spring 2.0的后繼改進。
1.1.autowire="byName" /"byType"
假設Controller有一個屬性名為customerDAO,Spring就會在配置文件里查找有沒有名字為CustomerDAO的bean, 自動為Controller注入。 如果bean有兩個屬性,一個想默認注入,一個想自定義,只要設定了autowire,然后顯式的聲明那個想自定義的,就可以達到要求。這就應了需求,在需要特別配置的時候就提供配置,否則給我一個默認注入。
還有一個更懶的地方,在最最根部的<beans>節點寫一句default-autovwrie="byName",可以讓文件里的所有bean 都默認autowrie。 不過Rod認為開發期可以這樣,但Production Server上不應該使用Autowire。而我覺得那些自定義一次的地方比如TranscationManager應該詳細定義,而Dao,Controller這種大量重復定義的bean就可以偷點懶了。
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 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語法簡化
最主要的簡化是把屬性值和引用bean從子節點變回了屬性值,對不喜歡autowire的兄弟比較有用。 當然,如果value要CDATA的時候還是要用子節點。另外,list的值可以用空格隔開也比較實用。
<property name="myFriendList">
<list>
<value>gigix</value> <value>wuyu</value>
</list>
</property>
簡化為
<property name="myFriendList" value="gigix wuyu"/>
 4.Spring 2.0來了 如果沒什么外力刺激,spring xml 可能就這樣不會變了。但現在xml成了過街老鼠,被ror的默認配置和JDK5的annotation逼得不行,當然就要繼續求變。 比如有好事者認為,節點名必須以bean打頭,附加一個屬性id來表示bean名;屬性值必須搞一個property子節點,子節點上有個屬性name來表示屬性名,是給機器看的很不直觀的東西。
<bean id="customerDAO" class="org.springside...CustomerDAO"> <property name="maxCount" value="10"> </bean> 給人看的東西應該就寫成
<customerDAO class="org.springside....CustomerDAO" maxCount="10"/> Spring 2.0正用schema實現類似的語法,具體請看它的JPetStore sample。
5.使用Spring自帶的DTD使編輯器Smart.
如果沒有用Eclipse的Spring插件,那至少也要使用spring自帶的dtd使XML編輯器smart一些,能夠自動為你生成屬性,判斷節點/屬性名稱有沒有拼錯等。
6.還有更變態的簡化配置方法 比如autoproxy,不過我覺得更簡化就不可控了,所以沒有采用。
相關文章 簡化Spring(1)--配置文件 簡化Spring(2)--Model層 簡化Spring(3)--Controller層 簡化Spring(4)--View層
作者:江南白衣
1.Groovy的最新八卦之處 1.1 Wiki: http://docs.codehaus.org/pages/listpages.action?key=GROOVY 1.2 Mail list的在線瀏覽和rss定閱 Developer List http://dir.gmane.org/gmane.comp.lang.groovy.devel User List: http://dir.gmane.org/gmane.comp.lang.groovy.user
2.Groovy的開發現狀和未來 編譯期健壯性大大增強的1.0 JSR-2,全力除Bug的1.0 JSR-3,JSR-4已經發布。 第二次全球Groovy 開發人員FB大會也在巴黎開完,有決議若干。 理論上 1.0正式版很快就要發布。 而計劃中的1.1版本將支持 ruby的maxin、continuations 和JDK1.5的Annotatin。
3.Groovy and Java的曖昧關系 作為Java的私生子,Groovy的最終可執行狀態是Java class bytecode,所以Java代碼里可以把它當普通Java Bean一樣調用。還有,Groovy的基礎類庫,都是用Java代碼來寫的。
3.1 編譯Java class bytecode 就像JSP的最終面目是servlet class,GroovyC也會把groovy文件編譯成Java class bytecode以在JVM上運行。 其中Groovy Class會編譯成GroovyObject的子類,Groovy Script代碼段會編譯成Script的子類。 可以用GroovyC來靜態編譯,也可以在Java程序里用GroovyShell動態parse Groovy文件。
3.2 在Java代碼中調用Groovy 1.Groovy類: GroovyObject類默認有get/setProperty()和invokeMethod()的反射接口,在Java代碼里,可以直接調用Groovy類的方法,也可以通過反射接口來調用。
2.Script代碼段: Script類有Script(Binding)構造函數和run()接口,在java里先通過Script(Binding)構造一個Script類,綁定變量,然后通過run接口進行調用。
3.3 在Java中直接使用Groovy的類庫 Groovy和Groovy的框架類庫都是用Java寫出來的的。所以有些類庫如SimpleTemplateEngine,也可以在Java里直接使用,不用繞Groovy一圈。
4.groovyJ插件 groovyJ是IDEA插件,有語法變色和Run()功能,更有用的功能是編譯Java文件目錄時,會把其中的groovy文件也一同編譯。而日后將支持重構、類間跳轉等功能,值得期待。 而Eclipse插件只有Run()和語法變色,而且隨著Eclipse的升級時靈時不靈,正在花時間實用化中。 NetBeans開了個Coyote的項目來支持腳本語言。
5.一些重要的開發人員 一個PM: Guillaume Laforge
兩個Founder: Bob Mcwhirter (同時是Drools,Dom4J的founder) , James Strachan (Core Developers Network,同時work on Geronimo,Drools,ServiceMix 和很多Jarkarta 項目,似乎擁有無窮的精力)
三個來自ThoughtWorks的開發人員: Jame Walnes,Chris Stevenson,Matt Foemmel
四個star of JSR-2: Jeremy Rayner, Jochen Theodorou,和兩位老大一起改進JSR-2編譯的強壯性和出錯信息顯示 Franck Rasolo:IDEA插件GroovyJ的開發人員 Christian Stein :Groovlet,Template的開發人員
Steven Devijver : Grails--Groovy on Rails的主持,同時是Spring Modules的leader。
6.有哪些使用Groovy的項目
Grails(Groovy on rails,大老們非常期待的項目,把rails在MVC和ORM的優點抄到java下,而且是基于Spring Framework的)
Drools(規則引擎, 用戶可以用groovy寫規則)
eXo platform(porlet/Service container,本身核心是groovy,用戶可以用groovy 來寫Porlet)
XWiki (第2代的wiki引擎、Application引擎。用戶可以用groovy寫Plug-in,Marco和Application)
RIFE(一個MVC/Web framework,用戶可以用groovy寫action和配置layout,workflow。還含有一個CRUD框架,用戶用groovy 定義domain class,滴嗒一下,就能獲得一個CRUD模型)
7.Groovy-all-1.0-jsr3.jar groovy需要asm和antlr包的支持,使用groovy-all-10-jsr3.jar,將預帶這兩個包的正確版本,非常省心。
8.Migrating to JSR JSR版本語法的的最大改動有兩處。網上很多Groovy文章都還是基于舊語法的,需要自己改正過來。 8.1 為了加強代碼健壯性,Class里的變量需要用def 定義。而在script里的變量因為是動態binding的,仍然不需要def定義。
8.2 多行的String需要用 """ """而不是" "來括住。
9.SimpleTemplateEngine-總共200行就實現了JSP engine的功能 動態語言開發框架很方便,所以Ruby on Rails沒有IDEA級的IDE都能這么就把MVC、ORM都實現了一遍。 請看src/groovy/text/SimpleTemplateEngine.java,總共219行。 原理就是把模版中的文本部分替換成println """ 文本""" ,Groovy部分照搬,生成一個新的Groovy script,然后拿GroovyShell執行一遍就完成了。
10.如何用Java實現動態語言
把GroovyC編譯出來的class文件再用jad反編譯,可以看到如何用Java去實現一門動態語言。 主要是多了一個MetaClass, 不斷的反射反射,運行時還非常依賴Asm
最簡單的例子: Groovy文件:
public class Customer { private String id; }
編譯出來的Java文件
public class Customer implements GroovyObject { private String id; transient MetaClass metaClass; public Customer() { Object obj = ScriptBytecodeAdapter.invokeStaticMethod ("org.codehaus.groovy.runtime.ScriptBytecodeAdapter", "getMetaClass", this); Metaclass metaclass = (MetaClass)ScriptBytecodeAdapter.asType(obj, groovy.lang.MetaClass.class); } public Object invokeMethod(String s, Object obj) { } public Object getProperty(String s) { .} 11.ant 的編譯腳本
<path id="groovy.classpath"> <pathelement path="${basedir}/ROOT/WEB-INF/classes/"/> <fileset dir="${basedir}/ROOT/WEB-INF/lib"> <include name="*.jar"/> </fileset> </path> <taskdef name="groovyc" classname="org.codehaus.groovy.ant.Groovyc"> <classpath refid="groovy.classpath"/> </taskdef> <target name="groovy"> <groovyc destdir="${project.basedir}/ROOT/WEB-INF/classes" srcdir="${project.basedir}/src" listfiles="true"> <classpath refid="groovy.classpath"/> </groovyc> </target>
作者:江南白衣
前篇:〈在Spring+Hibernate框架下,用動態語言寫業務類〉講述在Spring+Hibernate的架構下,因為動態語言所帶來的利益,把一部分業務類改用Groovy編寫并編譯成Java Class文件。 而且,因為Groovy的強大與簡便,加上與Java親密無間的關系,一些框架類也可以逐漸考慮用Groovy編寫。
1.雖然多是星零的好處,但忽然間一整噸好處擺在面前還是很讓人感動的。
除了動態語言和閉包、MOP,Groovy其他的特性多是對J2SE中設計不合理的地方逐一進行修正,集合、IO、字符串操作......雖然多是星零的好處,但忽然間以整噸好處擺在面前還是挺讓人感動的。
同時,Groovy完全兼容Java語法,但又提供糖糖選擇的方式感覺很貼心。(Groovy唯一不支持的java語法就是Inner Class的定義和函數定義里的"throws Exception" , 私生子的好處啊)
隱約覺得因為動態語言的無類型,還有閉包這樣帶著Lisp式FP的印記,加上MOP這樣的機制,可能會激發更大的變革發生。
1.動態類型 動態類型在Framework型項目中非常重要,多少設計模式嘔心瀝血,就是為了和這個類型搏斗。 而且,如果你是把代碼編譯成java Class,健壯性不會減低太多。 2.閉包 fp的基礎,沒有閉包的C++用函數指針,java用匿名內部類,都比他差遠了。 詳看Matin Flower <閉包>文章的中文版 ,在一段文件操作的Script中試演了一下,果然使代碼簡潔了好些。
3. MOP Groovy的Team Leader-- Guillaume Laforge最喜歡的一樣特性,groovy 嵌入式XML語法的基礎,對屬性和方法訪問的intercept 機制。詳看另一篇blog。 又比如,在MOP下,DAO那一堆findByName,findByTitle不用再逐一實現了,實現一個findBy即可攔截其他子虛烏有的findByXXX。
4. b.hql 可以多行,不用寫N多"和+ , 寫sql時特別方便。 c.簡單集成了正則表達式 If ("name"==~ "na.*") {println "match!"}
5. 集合與循環的語法 for (car in cars) { println car } for ( e in map ) { x += e.value}
或者 car.each{print it} 集合也可以直接定義了,如 def myList = ["Rod", 3, Date()] def myMap = ["Neeta":31, "Eric":34]
6.為JDK的基礎類擴展了一系列Helper方法 //原來StringTokenizer類的功能被簡單的集成到String類中了 names.tokenize(",").each{......} 其他基礎類的擴展見Groovy-JDK
7.簡化的Bean定義與賦值 //自動生成Getter和Setter class Customer { Integer id; String name; } //簡便的對象賦值 customer = new Customer(id:1, name:"calvin"); customer2 = new CUstomer(id:2); 重新使對象的屬性public,對java濫用getter,setter是一種修正。
8. Object內建的反射語法 customer.getAt("name") //得到屬性name, customer.invokeMethod("someFunction") //調用方法someFunction 從此不再需要Apache BeanUtils。
9.GPath--內置的XML語法,和Fremarker類似。 傳說中JDK7.0的功能 jdom和Dom4j可以安息了 book = new XmlSlurper().parseText("<book writer='calvin'><title>D</title></book>") println book.title; println book[@writer]; println book.children().size();
10.運算符重載 //向數組添加對象 params << customer.getAt(it); 還有如C++的向String,InputStream添加對象. 還有集合類相加,如list1+list2
11.簡化了IO操作
12.省略了每行末尾的分號 既然每行都要的,何必多此一舉呢? 另外return語句也可以省略,不過我還是習慣寫:)
2.Groovy版CustomerDAO的示例:
package com.itorgan.myappfuse.dao; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; public class CustomerDAOGroovy extends HibernateDaoSupport { public insert(customer) { getHibernateTemplate().save(customer) }
public List getAllValid(sortColumn) { def hql = """from Customer customer where customer.status='valid' order by ${sortColumn}""" def query = getSession().createQuery(hql) return query.list() }
public boolean isUnique(customer, uniqueColumnNames) { def params = [] def hql = "select count(*) from Customer customer where " def first = true uniqueColumnNames.tokenize(",").each { if (!first) hql += " or " else first = false hql+="customer.${it}=?"
params << customer.getAt(it) } def result = getHibernateTemplate().find(hql,params.toArray()) return ( result.get(0) == 0) } }
如果羨慕Ruby On Rails可以用動態語言來編碼,但又舍不得Spring、Hibernate這些Javaer深以為傲的框架,那么有一種折中的方案: 仍然使用Spring+Hibernate框架,而用Groovy/Jython/JRuby來編寫Controller類、Service類、DAO類,并把它們編譯成普通的Java Class文件來使用。 BuggyBean的blog里用Jython示范了這種方案。
1.why 動態語言? 現在的動態語言都已經很面向對象,和傳統的用來寫寫外圍腳本(測試,安裝)的script語言已經不同,而且Groovy,Jython,JRuby寫成的類除了動態載入外,都可以編譯成靜態的Java Class文件,所以已能很好的承擔J2EE應用里業務類的角色了。
動態語言有什么好處呢,請參看<Groovy寫業務類、框架類的那一噸好處>。
2. 八卦:各種動態語言的Java實現 Groovy ,BeanShell,Jython(Python),JRuby(Ruby),Rhino(JavaScript), Jacl(TCL),Bistro(SmallTalk),Kawa(Lisp/Schema)
3.哪種動態語言? Jython總是若斷若續,氣若游絲的樣子,而且現在都才移植到Jython2.1,比Python2.4 慢了幾拍,很信不過。
JRuby呢? Dion Almaer在JRuby0.8.2發布時說:"The day JRuby gets up to Jython levels, and then C Ruby levels, will be a great day.",字面上JRuby比Jython還要不靠譜。 Ruby還有一個不好是暫時沒有好的IDE(連那個收費的ruby-ide都好弱),如果沒有好的IDE,可以抵消掉大部分語言本身的優勢,真不能想像Ruby On Rails是用怎么個UltraEdit級的編輯器寫出來的。
Groovy的弱勢是1.0正式版還沒有發行,用戶社區還不大。
因為現在選的是Java框架下嵌入哪種動態語言作為業務類。所以Python和Ruby的用戶社群和大量的已有項目作用不是很大。而Groovy比起兩位舶來品, 1.作為私生子,嵌入性理所當然是最好的,兩者的關系曖昧得不得了。 2.另一個天然優勢是能兼容Java的語法,把Java代碼copy到Groovy幾乎不作什么修改(only不支持throws定義語句和Inner Class),團隊的學習曲線平滑。 3.因為不是移植項目,語言升級時沒有時間差,不用看移植人的臉色。
so,我會選Groovy,等它的正式版出來之后。
作者: 江南白衣 Groovy的Team Leader-- Guillaume Laforge說,MOP(Meta Object Protocol)是他最喜歡的Groovy特性。 MOP是對屬性、方法進行攔截解釋的簡單機制,intercept 已經因為AOP而被大家熟悉。 Groovy的類都繼承于GroovyObject,GroovyObject有get/setProperty()和invokeMethod()兩個函數,當客戶調用不存在的屬性和方法時,就會交由這兩個函數來處理,在Ruby里,這個方法被更貼切的命名為method_missing()。Groovy類可以通過重載這兩個函數,加入自己的hook和behavior,比Java簡單的多的實現 Proxy和 Delegator。
而更重要的是,MOP函數可以充當領域語言解釋者的角色,攔截一些存在于領域語言的而在Class里根本沒有定義的屬性、方法來進行解釋,這就是Groovy里XML嵌入式語法的魔法根源。 IBM DW有一篇專門的文章 :《PRACTICALLY mini-languages and MOPs Of Groovy:》 比如如下的xml
<shop> <book name="foo"> <writer>莊表偉< SPAN>writer> < SPAN>book> < SPAN>shop>
可以用groovy這樣訪問
println node.book.writer.text()
node類當然沒有book,writer這樣屬于領域語言的屬性,但通過重載getPropety(String name)函數,node類可以把對book,writer屬性的訪問,轉換成相應DOM節點的訪問。 實現請參看org.codehaus.groovy.sandbox.util.XMLList類的public Object getProperty(final String elementName)函數。
Guillaume Laforge說,It's an aspect of Groovy which makes the language very powerful, but rare are those who really know and use it.
|
|
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
---|
27 | 28 | 29 | 30 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
公告
常用鏈接
隨筆分類
隨筆檔案
朋友
積分與排名
最新評論

閱讀排行榜
|
|