開發,從需求出發 · 之四 春天在這裏
首先,我要大字標語表達立場:
你所使用的framework & non-core features,就跟女人穿在身上的衣服一樣,越少越好!
扯淡完成,說正經的。
讓我們繼續盯著花姐——啊,不——是 BeanFactory看。
public static SearchService getSearchService() { if(MOCK) { return new SearchServiceMock(); } else { LuceneDAO luceneDAO = getLuceneDAO(); MysqlDAO mysqlDAO = getMysqlDAO(); return new SearchServiceInRealBiz(luceneDAO, mysqlDAO); } }
有木有點兒標題所說的“春天在這裏”的意思了?
納尼?沒看出來?
好吧,或許你習慣了spring的xml裝配方式,所以認為把兩者關聯起來看實在須要超常的想象力,那麽,
我把BeanFactory改頭換面。簡單的實現一個基於文本字符串的裝配,咋們再來看看效果:
package cn.com.sitefromscrath; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.List; import java.util.Map; public class BeanFactory { private static Map<String, String> appBean = new HashMap<String, String>(); private static Map<String, String[]> appRef = new HashMap<String, String[]>(); static { appBean.put("luceneDAO", "cn.com.sitefromscrath.dao.LuceneDAOMock"); appBean.put("mysqlDAO", "cn.com.sitefromscrath.dao.MysqlDAOMock"); appBean.put("searchService", "cn.com.sitefromscrath.service.SearchServiceInRealBiz"); appRef.put("searchService", new String[]{"luceneDAO", "mysqlDAO"}); } public static Object getBean(String id) { try { String className = appBean.get(id); Class clazz = Class.forName(className); Constructor constructor; String[] ref = appRef.get(id); if(ref == null || ref.length == 0) { constructor = clazz.getConstructor(); return (Object)constructor.newInstance(); } Class[] parameterTypes = new Class[ref.length]; Object[] initargs = new Object[ref.length]; for(int i = 0; i < ref.length; i++) { String r = ref[i]; String rclassName = appBean.get(r); parameterTypes[i] = Class.forName(rclassName).getInterfaces()[0]; //這裏我偷懶了:) initargs[i] = getBean(r); } constructor = clazz.getConstructor(parameterTypes); return (Object)constructor.newInstance(initargs); } catch (Exception e) { e.printStackTrace(); return null; } } public static void main(String ... arg) { LuceneDAO luceneDAO = (LuceneDAO) getBean("luceneDAO"); int[] vals = luceneDAO.findDocIDs("test"); for(int v : vals) { System.out.println(v); } String keywords = "test"; SearchService searchService = (SearchService)getBean("searchService"); List results = searchService.search(keywords); for(int i = 0; i < results.size(); i++) { Result result = (Result) results.get(i); System.out.print("[" + result.title + "]"); System.out.println(result.content); } } }
執行結果輸出:
1 2 3 4 [result 1]something.................. [result 2]something.................. [result 3]something.................. [result 4]something..................
結果正確!
再看看我們對類的裝配:
private static Map<String, String> appBean = new HashMap<String, String>(); private static Map<String, String[]> appRef = new HashMap<String, String[]>(); static { appBean.put("luceneDAO", "cn.com.sitefromscrath.dao.LuceneDAOMock"); appBean.put("mysqlDAO", "cn.com.sitefromscrath.dao.MysqlDAOMock"); appBean.put("searchService", "cn.com.sitefromscrath.service.SearchServiceInRealBiz"); appRef.put("searchService", new String[]{"luceneDAO", "mysqlDAO"}); }
請比較和spring.xml的差別:
<?xml version="1.0" encoding="UTF-8"?> <beans > <bean id="luceneDAO" class="cn.com.sitefromscrath.dao.LuceneDAOMock" /> <bean id="mysqlDAO" class="cn.com.sitefromscrath.dao.MysqlDAOMock" /> <bean id="searchService" class="cn.com.sitefromscrath.service.SearchServiceInRealBiz"> <constructor-arg index="1" ref="luceneDAO" /> <constructor-arg index="2" ref="mysqlDAO" /> </bean> </beans>
好吧,沒什麽根本上的差別,我們相同可以解析xml,得到我們自己實現的BeanFactory所須要的一切要素。
好了。經過我們將代碼從零開始,重復重構,到一個比較“經典”的模式。我們找到了“春天”。這也是spring的core features。
或許是對spring看待的角度不同,我發現我對spring的依賴註入和控制反轉的用途和不少人並不一致。下一章。我打算結合一些開發和測試技巧,論述一下我的看法。
只是。如今,是吐槽spring的時間:
我在我的博文《thinking in asp》appendix A - 吐槽JAVA 中以前說到:
還有新版本號的spring,怎麽說呢,它把java的annotation機制玩兒到了“奇技淫巧”的程度。
因為那個系列主要是講視頻編轉碼技術。因此。對spring僅僅是一帶而過,如今總算找到機會了:)
Long long ago, in the old good times, spring還是依賴xml做裝配的小姑娘,青春漂亮,帶著點兒書卷學生氣。
可是如今看,這丫頭已經塗脂抹粉,躋身上流社會。出入商務場合。盡管不討厭,可是不讓人認為親近了 -_-b
看傳統的 spring.xml,就如同看電路板設計圖,一個個元器件清清楚楚,什麽型號,怎麽走線。怎樣裝配。盡管沒有圖形化。可是一目了然,基本上能夠做到不用查看源代碼。就能把整個系統的邏輯關系梳理的八九不離十。
而自從有了annotation,比方autowire。xml不重要了,你無法再從一個基於spring的大型項目中迅速找到一份天然的“提綱”。
——spring開始不顧一切的媚俗。——或許俺是個受虐狂,只是“ruby way”和“傲嬌”的python顯然更對我的胃口——設計原則是不能夠松口的:)
我以前問spring的一些使用者,怎樣找到通過annotation裝配尤其是自己主動裝配的類,或者是某個隱藏在spring-mvc框架下annotation聲明的URL,
給我的答案例如以下:
首先:
然後:
好吧。我得到的結論是:與其如此,不如不用:)
當然,假設你說。通過一些詞法/語法解析器,也能夠得到基於annotation的“提綱”。比方用 lex+yacc 亦或 antlr 打造一個工具。
本座的答復是:老子被編譯原理搞的幾宿沒睡了,小心一指頭把你戳出去三公裏遠去~~~!
接著。說說spring框架下怎樣強測試的問題:
時刻記住。每個模塊。甚至最“小”的方法,在實現它之前,都必需要先設計怎樣測試它。
由於我們如今討論的是一個web項目,我想說一個非常多開發人員會使用的方法:
ContextListener --> WebApplication --> BeanFactory
因為spring的“人性化”。這個步驟甚至不須要你寫代碼。
如今的問題是,假設我們的項目採取這種方式。你怎樣做dao 或者 service層的單元測試?
比方前面提到的 SearchService,他須要通過spring裝配LuceneDAO 和 MysqlDAO,可是問題出現了:
你假設想讓SearchService的方法跑起來,你必須啟動TOMCAT等web容器!
這樣的緊耦合的程度簡直是令人發指 :) 讓一切單元測試成為不可能~~~~
而我,在前面的章節重復強調 “不須要啟動tomcat。不須要查看網頁的實際效果,也能保證系統模塊的正確” 就是這個意思:)
當然。spring提供了ApplicationContext,在web容器中相同能用——這也是我使用的方式——可是,我想說的是。spring的WebApplication模式錯誤的誘導了開發人員。引發了大量的 bad smell。
在玩兒了一把怎樣從最簡單的需求出發。重復重構到模式/框架之後,下一章我會再次繞回去,spring已經說得夠多的啦:)
to be continued.....
開發,從需求出發 · 之四 春天在這裏