在当今的MVC framework里似乎Webwork逐渐成为主流 Webwork+SpringFramework的组合变得越来越流行这似乎意味着Spring自带的MVC framework远比Webwork差所以大家纷纷用Webwork来代替确实Spring的MVC framework不算是整个Spring的核心部件但它的威力却超过了很多人的想象很多人包括xiecc认为Spring的MVC framework是非常优秀的甚至比Webwork更优秀
下面列举一下Spring的MVC framework在设计时做出的一些重要的决定并将之和相关的MVC framework如Webwork或struts进行对比
一Spring的整个MVC配置是基于IOC容器的
与struts或webwork相比这是一个ms有点奇怪的决定看一下Spring MVC的配置文件最先看到的不是action或者form而是一些有着特定名字的beanBean下面的配置是一些简单或有点复杂的属性我们看到的是机器更容易的数据结构而不是人更容易理解的元素
但是这恰恰是Spring的MVC强大的根源!因为它的配置就是Spring的核心IOC容器的配置这意味着所有IOC容器的威力都可以在这里展现我们可以为所欲为地对Spring MVC进行扩展和增强我们可以完成在其它MVC framwork中很多难以想象的任务想扩展新的URL映射方式吗?要换一个themeResolver或LocalReolver的实现吗?想在页面中显示新类型的View(比如说RDF呵呵一个小秘密xiecc是研究语义网的虽然成天不务正业不写论文只写八卦)?甚至想直接在Controller里定义AOP吗?这些对Spring的MVC来说都是小菜一碟
我没有仔细研究过Webwork的扩展机制我知道通过Webwork的interceptor机制可以进行很多的扩展甚至有一个简单简单的IOC容器但不管它有多强大提供了多少扩展点它的威力都很难和真正的IOC容器相比而struts的plugin功能则是出名的滥虽然它也提供了plugin机制
Spring采用IOC配置的另一个原因是使Spring的MVC与Spring的IOC容器的整合变得非常的容易Spring提供了与struts与webwork的整合但是这样整合都需要在进行间接的包装感觉总不是很自然而且还会导致一个概念多个配置webwork就需要在Spring里配置bean再配置自己的xwork文件想象一下吧我们的bean直接就是一个controller直接可以完成MVC的所有任务这是多少爽的感觉
Rod Johnson采用IOC容器来实现的另一个原因是这会减少好多开发工作量看一下urlMapping吧它提供的property本身就是一个HashMap只有配置完成我们的bean里的数据就自然存在了哈哈好爽吧不用象struts那样解析XML再把它的内容一项一项地读到HashMap里
虽然这样的配置会有点怪异但假如我们对Spring的IOC容器非常熟悉的话会发现它非常的亲切也非常的简单
最后是一个简单的小秘密Spring怎么知道某个bean的配置就是urlMapping?另一个bean的配置就是viewResolver?其实很简单把所有的bean全部读到内存里然后通过bean的名字或类型去找就行了通过名字去找就是简单的getBean方法通过类型去找则使用了BeanFactoryUtilsbeansOfTypeIncludingAncestors的静态方法
二Spring提供了明确的Model View概念和相应的数据结构
在Spring里有一个有趣的数据类型叫做ModelAndView它只是简单地把要显示的数据和显示的结果封装在一个类里但是它却提供了明确的MVC概念尤其是model概念的强化使程序的逻辑变得更清晰了
记得以前在Struts里写程序里的时候为了显示数据经常自己把东西放到HttpSession或HttpServletRequest里(或set到form里虽然不太有用)这造成了model概念的模糊而且也导致了struts与JSP页面的紧耦合假如我们要替换成Veloctiy就得另外加一个plugin因为在velocity里数据是不需要不放到request里的
Webwork里强调的是与Web framework解耦和它的command模式的简单性因此在它的action里只有简单的get或set方法假如返回数据也只是简单地返回一个String当然这样的实现有它的好处但是它淡化了model和view的概念Rod Johnson认为Webwork里的Action同时包含了Action和Model的职责这样一个类的职责太多不是一个很好的设计当然Jason Carreira不太认同这种观点因为Action里的model对象完成可以delege给其它对象但不管怎样这种争论的根源在于Webwork里淡化了model view甚至web的概念仁者见仁智者见智最后的结果还是看个人喜欢好吧
三Spring的Controller是Singleton的或者是线程不安全的
和Struts一样Spring的Controller是Singleton的这意味着每个request过来系统都会用原有的instance去处理这样导致了两个结果我们不用每次创建Controller减少了对象创建和垃圾收集的时间由于只有一个Controller的instance当多个线程调用它的时候它里面的instance变量不是线程安全的
这也是Webwork吹嘘的地方它的每个Action都是线程安全的因为每过来一个request它就创建一个Action对象由于现代JDK垃圾收集功能的效率已经不成问题所以这种创建完一个对象就扔掉的模式也得到了好多人的认可Rod Johnson甚至以此为例证明JEE提供的object pool功能是没多大价值的
但是当人们在吹嘘线程安全怎么怎么重要的时候我想请问有多少人在多少情况下需要考虑线程安全?Rod Johnson在分析EJB的时候也提出过其它问题并不是没有了EJB的线程安全魔法世界就会灭亡的大多数情况下我们根本不需要考虑线程安全的问题也不考虑object pool因为我们大多数情况下不需要保持instance状态
至少我写了那么多的struts Action写了那么多的Spring Controller几乎没有碰到需要在instance变量保持状态的问题当然也许是我写的代码不够多Struts的设计者Craig R McClanahan曾经说当时他设计struts时有两个条件不成熟当时没有测试驱动开发的概念当时JVM的垃圾收集性能太次假如现在重新设计的话他也会采用每个request生成一个新对象的设计方法这样可以解决掉线程安全的问题了
四Spring不象Webwork或tapestry那样去隐藏Servlet相关的元素如HttpServletRequest或HttpServletResponse
这又是一个重要的设计决定在Webwork里我们没有HttpServletRequest或者HttpServletResponse只有getter setter或ActionContext里数据这样的结果导致一个干净的Action一个与Web完全无关的Action一个可以在任何环境下独立运行的bean那么Webwork的这样一个基于Command模式的Action究竟给我们带来了什么?我想主要有两点
它使我们的Action可以非常容易地被测试
用户可以在Action里添加业务逻辑并被其它类重用
然而仔细跟Spring比较一下我们就会发现这两点功能所带来的好处其实并不象我们想象的那么显着Spring的Controller类也可以非常轻松被测试看一下springmock下面的包吧它提供的MockHttpServletRequest MockHttpServletResponse还有其它一些类让测试Controller变得异常轻松再看一下Action里的业务逻辑吧Jason Carreira曾经说我们可以尽情地在Webwork的Action里加业务逻辑因为Action是不依赖于Web的但是有多少人真正往Action里加业务逻辑的?大多数人都会业务逻辑delegate给另一个Service类或Manager类因为我们很清楚往Action里加业务逻辑会使整个体系的分层架构变得不清晰不管怎样Web层就是Web层业务层就是业务层两者的逻辑混在一起总会带来问题的而且往Action里加业务逻辑会使用这个Action类变得庞大Webwork的Action是每个request都创建实例的尽管带来的性能影响不太大但并不表示每次都要把业务逻辑再new出来业务逻辑在大多数的情况下应该是单例的
不把request和response展现给用户当然还会带来功能上的损失也许一般的场合用用webwork提供的接口已经足够了但有时我们必须要知道request和response才能发挥出更大的威力比如我以前的一个项目里有一个通过递归动态生成的树状结构的页面在jsp页面上显示递归是痛苦或不可能的因此我用response直接write出页面这在spring里很easy但在webwork里可能比较难了(偶不敢肯定偶研究得不够深也许高手是有办法的)
五Spring提供了不错但不够充分的interceptor机制
回头看一下struts它在架构里甚至没有给我们提供hook point的机会我们没有任何机会加入自己的interceptor我们只能通过重载struts的RequestProcessor类来进行一点有限的扩展
到了Webwork似乎interceptor一下子成了整个Framework的核心除了Action的核心部件其它所有的东西都是interceptor它的超强的interceptor功能使们扩展整个架构变得非常方便有人称这种interceptor为AOPJason Carreira则自豪地宣称这个叫做pragamtic AOP我不认同这是AOP它只是简单的interceptor机制但不管如何它的interceptor确实有强大的功能
Spring也提供了它的interceptor机制它的HandlerInterceptor三个interceptor方法peHandle postHandle afterCompletion分别对应Controller执行前Controller执行后和page render之后虽然大多数情况下已经够用但是从功能上来说显然它没有Webwork强大从AOP的角度来看它没有提供around interceptor而只有before与after interceptor这意味着我们无法在interceptor前后保持状态最简单的情况假如我们要计算一个Controller的执行时间我们必须在执行完before后把begintime这个状态保持住再在after里把它调出来但是显然这个状态保持会是个问题我们不能把它放到instance变量里因为interceptor不是线程安全的也许通过ThreadLocal可以解决这个问题但是如此简单的功能要用到这样的方法来处理显然这个Interceptor本身设计上还是有点问题的
六Spring提供了MultiActionController使它可以在一个类里包含多个Action
这个设计和struts的DispatchAction有点类似只不过提供了更灵活的机制当我们的项目变大的时候把功能类似的方法放到同一个Action里完全值得的!Webwork缺少这样的机制假如看一下Spring的源代码会发现其实实现MultiActionController的工作量相当的少只不过是用反射机制把解析出来的方法名执行一下就完事了其实Webwork也完全可以提供这样的机制虽然从设计上来说确实不是很优雅但是它确实很有用
七Spring提供了更多的选择方式
看看Spring里提供的Controller吧它提供了好多不同的Controller类要生成Wizard吗?要专门用于提交form的Controller吗?要执多个方法的类吗?Spring提供了丰富的子类来扩展这些选择当然我们还可以很轻松地自己扩展这些功能
再看看Spring的ViewResolver吧 它提供了无数不同类型的ViewResolver更重要的是我们自定义我们的页面映射方式看看strtus看看webwork都会存在页面与forward name的一层间接转换我们必须在配置文件里配置好某个字符串(典型的是success)对应的是那个页面但是Spring里我们有了更大的自由度我们可以采用webwork的策略也可以采用更简单的策略如将JSP文件名去掉扩展名的映射方法也许有人认为这种映射方式很幼稚但是我觉得它是非常有用的方式即使在大项目里
还有新的扩展吗?看看Spring Web Flow吧它是SpringFramework的子项目它为一长串的基于页面流的Wizard页面提供了可配置的实现方式在Spring 里它将是SpringFramework的一部分
八Spring的tag
尽管Spring的tag数量上少得可怜但它却是精心设计的它的目标很简单让美工可以轻松地编辑页面因为在Spring的页面里Text仍然是Textcheckbox仍然是CheckBox而不象在struts或webwork中的Tag它只是用Springbind对输入内容进行了一下包装所以尽管页面显示代码上会比Webwork多但这绝对是有价值的
在接下来的几章里我会分析一下Spring是如何让我们的Web应用不需要知道ApplicationContext就能够访问IOC容器的然后会对Spring的设计和执行过程进行简单的源码分析然后给出几个扩展Spring MVC的方法