首先,我要大字標語表達立場:
你所使用的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.....