首页 > 代码库 > 开发,从需求出发 · 之四 春天在这里

开发,从需求出发 · 之四 春天在这里

首先,我要大字标语表达立场:

你所使用的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 taste。


在玩儿了一把如何从最简单的需求出发,反复重构到模式/框架之后,下一章我会再次绕回去,spring已经说得够多的啦:)

to be continued.....