Spring MVC framework深入分析之一--总体分析
leopallas | 08 九月, 2005 14:54
在当今的MVC framework里,似乎Webwork2逐渐成为主流,
Webwork2+SpringFramework的组合变得越来越流行。这似乎意味着Spring自带的MVC
framework远比Webwork2差,所以大家纷纷用Webwork2来代替。确实,Spring的MVC
framework不算是整个Spring的核心部件,但它的威力却超过了很多人的想象。很多人包括xiecc认为Spring的MVC
framework是非常优秀的,甚至比Webwork2更优秀。
下面列举一下Spring的MVC framework在设计时做出的一些重要的决定,并将之和相关的MVC framework如Webwork2或struts进行对比:
一、 Spring的整个MVC配置是基于IOC容器的
与
struts或webwork2相比,这是一个ms有点奇怪的决定,看一下Spring
MVC的配置文件,最先看到的不是action或者form,而是一些有着特定名字的bean,Bean下面的配置是一些简单或有点复杂的属性。我们看到
的是机器更容易的数据结构,而不是人更容易理解的元素。
但是这恰恰是Spring的MVC强大的根源!因为它的配置就是Spring的核心IOC
容器的配置,这意味着所有IOC容器的威力都可以在这里展现,我们可以为所欲为地对Spring MVC进行扩展和增强,我们可以完成在其它MVC
framwork中很多难以想象的任务。想扩展新的URL映射方式吗?要换一个themeResolver或LocalReolver的实现吗?想在页面
中显示新类型的View(比如说RDF,呵呵,一个小秘密:xiecc是研究语义网的,虽然成天不务正业,不写论文,只写八卦)?甚至想直接在
Controller里定义AOP吗?这些对Spring的MVC来说都是小菜一碟。
我没有仔细研究过Webwork2的扩展机制,我知道通过
Webwork2的interceptor机制,可以进行很多的扩展,甚至有一个简单简单的IOC容器。但不管它有多强大,提供了多少扩展点。它的威力都
很难和真正的IOC容器相比。而struts的plugin功能则是出名的滥,虽然它也提供了plugin机制。
Spring采用IOC配置的另
一个原因是使Spring的MVC与Spring的IOC容器的整合变得非常的容易。Spring提供了与struts与webwork2的整合,但是这
样整合都需要在进行间接的包装,感觉总不是很自然。而且还会导致一个概念多个配置,webwork2就需要在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方法,通过类型去找则使用了
BeanFactoryUtils.beansOfTypeIncludingAncestors的静态方法。
二、 Spring提供了明确的Model, View概念和相应的数据结构
在Spring里有一个有趣的数据类型叫做ModelAndView,它只是简单地把要显示的数据和显示的结果封装在一个类里。但是它却提供了明确的MVC概念,尤其是model概念的强化,使程序的逻辑变得更清晰了。
记
得以前在Struts里写程序里的时候,为了显示数据经常自己把东西放到HttpSession或HttpServletRequest里(或set到
form里,虽然不太有用),这造成了model概念的模糊,而且也导致了struts与JSP页面的紧耦合。假如我们要替换成Veloctiy,就得另
外加一个plugin,因为在velocity里数据是不需要不放到request里的。
Webwork2里强调的是与Web
framework解耦和它的command模式的简单性,因此在它的action里只有简单的get或set方法,假如返回数据,也只是简单地返回一个
String。当然这样的实现有它的好处,但是它淡化了model和view的概念。Rod
Johnson认为Webwork2里的Action同时包含了Action和Model的职责,这样一个类的职责太多,不是一个很好的设计。当然
Jason
Carreira不太认同这种观点,因为Action里的model对象完成可以delege给其它对象。但不管怎样,这种争论的根源在于
Webwork2里淡化了model, view甚至web的概念。仁者见仁,智者见智,最后的结果还是看个人喜欢好吧。
三、 Spring的Controller是Singleton的,或者是线程不安全的
和
Struts一样,Spring的Controller是Singleton的,这意味着每个request过来,系统都会用原有的instance去处
理,这样导致了两个结果:我们不用每次创建Controller,减少了对象创建和垃圾收集的时间;由于只有一个Controller的
instance,当多个线程调用它的时候,它里面的instance变量不是线程安全的。
这也是Webwork2吹嘘的地方,它的每个
Action都是线程安全的。因为每过来一个request,它就创建一个Action对象。由于现代JDK垃圾收集功能的效率已经不成问题,所以这种创
建完一个对象就扔掉的模式也得到了好多人的认可。Rod Johnson甚至以此为例证明J2EE提供的object pool功能是没多大价值的。
但
是当人们在吹嘘线程安全怎么怎么重要的时候,我想请问有多少人在多少情况下需要考虑线程安全?Rod
Johnson在分析EJB的时候也提出过其它问题,并不是没有了EJB的线程安全魔法,世界就会灭亡的,大多数情况下,我们根本不需要考虑线程安全的问
题,也不考虑object pool。因为我们大多数情况下不需要保持instance状态。
至少我写了那么多的struts
Action,写了那么多的Spring
Controller,几乎没有碰到需要在instance变量保持状态的问题。当然也许是我写的代码不够多,Struts的设计者Craig R.
McClanahan曾经说当时他设计struts时有两个条件不成熟:当时没有测试驱动开发的概念;当时JVM的垃圾收集性能太次。假如现在重新设计的
话,他也会采用每个request生成一个新对象的设计方法,这样可以解决掉线程安全的问题了。
四、 Spring不象Webwork2或tapestry那样去隐藏Servlet相关的元素如HttpServletRequest或HttpServletResponse
这
又是一个重要的设计决定。在Webwork2里我们没有HttpServletRequest或者HttpServletResponse,只有
getter,
setter或ActionContext里数据,这样的结果导致一个干净的Action,一个与Web完全无关的Action,一个可以在任何环境下独
立运行的bean。那么Webwork2的这样一个基于Command模式的Action究竟给我们带来了什么?我想主要有两点:
1、 它使我们的Action可以非常容易地被测试。
2、 用户可以在Action里添加业务逻辑,并被其它类重用。
然
而仔细跟Spring比较一下,我们就会发现这两点功能所带来的好处其实并不象我们想象的那么显著。Spring的Controller类也可以非常轻松
被测试,看一下spring-mock下面的包吧,它提供的MockHttpServletRequest,
MockHttpServletResponse还有其它一些类让测试Controller变得异常轻松。再看一下Action里的业务逻辑
吧,Jason
Carreira曾经说我们可以尽情地在Webwork2的Action里加业务逻辑,因为Action是不依赖于Web的。但是有多少人真正往
Action里加业务逻辑的?大多数人都会业务逻辑delegate给另一个Service类或Manager类。因为我们很清楚,往Action里加业
务逻辑会使整个体系的分层架构变得不清晰,不管怎样,Web层就是Web层,业务层就是业务层,两者的逻辑混在一起总会带来问题的。而且往Action里
加业务逻辑会使用这个Action类变得庞大,Webwork2的Action是每个request都创建实例的,尽管带来的性能影响不太大,但并不表示
每次都要把业务逻辑再new出来,业务逻辑在大多数的情况下应该是单例的。
不把request和response展现给用户当然还会带来功能上的
损失,也许一般的场合,用用webwork2提供的接口已经足够了,但有时我们必须要知道request和response才能发挥出更大的威力。比如我
以前的一个项目里有一个通过递归动态生成的树状结构的页面,在jsp页面上显示递归是痛苦或不可能的,因此我用response直接write出页面,这
在spring里很easy,但在webwork里可能比较难了(偶不敢肯定,偶研究得不够深,也许高手是有办法的)。
五、 Spring提供了不错但不够充分的interceptor机制
回头看一下struts,它在架构里甚至没有给我们提供hook point的机会,我们没有任何机会加入自己的interceptor。我们只能通过重载struts的RequestProcessor类来进行一点有限的扩展。
到
了Webwork2,似乎interceptor一下子成了整个Framework的核心,除了Action的核心部件,其它所有的东西都是
interceptor。它的超强的interceptor功能使们扩展整个架构变得非常方便。有人称这种interceptor为AOP,Jason
Carreira则自豪地宣称这个叫做pragamtic
AOP。我不认同这是AOP,它只是简单的interceptor机制。但不管如何,它的interceptor确实有强大的功能。
Spring
也提供了它的interceptor机制,它的HandlerInterceptor三个interceptor方法:peHandle,
postHandle, afterCompletion。分别对应Controller执行前,Controller执行后和page
render之后。虽然大多数情况下已经够用,但是从功能上来说显然它没有Webwork2强大。从AOP的角度来看,它没有提供around
interceptor,而只有before与after
interceptor。这意味着我们无法在interceptor前后保持状态,最简单的情况假如我们要计算一个Controller的执行时间,我们
必须在执行完before后把begintime这个状态保持住,再在after里把它调出来,但是显然这个状态保持会是个问题,我们不能把它放到
instance变量里,因为interceptor不是线程安全的。也许通过ThreadLocal可以解决这个问题,但是如此简单的功能要用到这样的
方法来处理,显然这个Interceptor本身设计上还是有点问题的。
六、 Spring提供了MultiActionController,使它可以在一个类里包含多个Action
这
个设计和struts的DispatchAction有点类似,只不过提供了更灵活的机制。当我们的项目变大的时候,把功能类似的方法放到同一个
Action里完全值得的!Webwork2缺少这样的机制。假如看一下Spring的源代码,会发现其实实现
MultiActionController的工作量相当的少,只不过是用反射机制把解析出来的方法名执行一下就完事了。其实Webwork2也完全可以
提供这样的机制。虽然从设计上来说确实不是很优雅,但是它确实很有用。
七、 Spring提供了更多的选择方式
看看Spring里
提供的Controller吧,它提供了好多不同的Controller类。要生成Wizard吗?要专门用于提交form的Controller吗?要
执多个方法的类吗?Spring提供了丰富的子类来扩展这些选择。当然我们还可以很轻松地自己扩展这些功能。
再看看Spring的
ViewResolver吧,
它提供了无数不同类型的ViewResolver。更重要的是我们自定义我们的页面映射方式。看看strtus,看看webwork2,都会存在页面与
forward
name的一层间接转换,我们必须在配置文件里配置好某个字符串(典型的是success)对应的是那个页面。但是Spring里我们有了更大的自由度,
我们可以采用webwork2的策略,也可以采用更简单的策略,如将JSP文件名去掉扩展名的映射方法。也许有人认为这种映射方式很幼稚,但是我觉得它是
非常有用的方式,即使在大项目里。
还有新的扩展吗?看看Spring Web Flow吧,它是SpringFramework的子项目。它为一长串的基于页面流的Wizard页面提供了可配置的实现方式。在Spring 1.3里,它将是SpringFramework的一部分。
八、 Spring的tag
尽
管Spring的tag数量上少得可怜,但它却是精心设计的。它的目标很简单:让美工可以轻松地编辑页面。因为在Spring的页面里Text仍然是
Text,checkbox仍然是CheckBox,而不象在struts或webwork2中的Tag。它只是用Springbind对输入内容进行了
一下包装。所以尽管页面显示代码上会比Webwork2多,但这绝对是有价值的。
在接下来的几章里,我会分析一下Spring是如何让我们的Web应用不需要知道ApplicationContext就能够访问IOC容器的,然后会对Spring的设计和执行过程进行简单的源码分析,然后给出几个扩展Spring MVC的方法。
Spring MVC framework深入分析之二--ApplicationContext之谜
leopallas | 09 九月, 2005 20:25
假如我们在写一个基于Spring的普通应用程序,不管我们用了多么精妙的设计模式,进行了如何巧妙的设计,我们必须在某个地方执行这样的代码:
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(
new String[] {"applicationContext.xml", "applicationContext-part2.xml"});
appContext.getBean("…");
也
许这样的代码算不上丑陋,但是它无疑破坏了程序的纯洁性和透明性。我们的应用程序开始显式地依赖SpringFramework,我们必须清楚地知道
Spring的配置文件有哪几个,每个配置文件的加入或修改源代码,我们必须在某些代码模块里调用丑陋的getBean方法来创造对象。
但
是所有的这些丑陋的事情似乎在我们的Web应用程序里消失啦,所有的代码都是那么干净,只有简单的get与set及接口之间的调用,我们不需要知道
ApplicationContext,我们甚至不需要知道Spring。但是我们所有的对象却又是通过Spring的
ApplicationContext来创造的!
看上去似乎很神奇,但是假如我们稍微思考一下,就会发现这是一件合情合理又如此简单的事情,呵呵,只有第一个想到这个方法的人才是伟大的。让我们仔细想一下普通应用程序和Web应用程序的最大区别在哪里?
其
实真正的区别只有一个,普通应用程序是一个主动执行的程序,而Web应用程序却是被动的组件。这意味着Web应用程序无法自己主动去生成自己的线程去执行
某项任务,而必须借用Web容器中的一个线程。想象一下一个简单的任务:我们想每隔一段时间执行一个任务,比如说在Console里打印出一行文字。在我
们的Web应用程序里应该怎么完成?在我不知道Servlet
Listener或Spring里提供的Schedule之前(其实Spring就是利用Servlet Listner初始化Application
Context时启动schedule的),这么简单的任务在一个Web应用程序里竟然是不可想象。还记得我当时采用的是最傻的做法:写了一个单独的应用
程序,在这应用程序的main函数里启动了timetask。
但是如果换一种角度来看,整个Web应用程序生活在容器里也给我们带
来了额外的好处,当我们让出了对应用程序的控制权之后,我们可以让容器帮我们完成很多本来很难处理的事情。其实IOC容器的真正作用也在于此,当我们把我
们的对象创建工作移交给IOC容器之后,我们发现整个程序变得如此清晰,如此透明,对象之间的关联、哪些类需要事务处理或AOP功能、哪些类要远程访问,
所有这些复杂的事情在我们的程序里都不见了,我们只看到了简单的get和set。
也许废话太多,但我觉得经过这样分析,其实
ApplicationContext之谜已经不再是谜了。真正的关键在于当我们的Web应用程序是被动的组件时,它除了可以错用容器的线程之外还可以错
用其它一些东西。我们可以让容器来帮我们创建ApplicationContext,然后把它放在某个地方,然后在需要使用时让容器从这个地方把
ApplicationContext读出来,并执行相应的Controller就可以了。
这个"某个地方"就是ServletContext,而这个创建ApplicationContext的地方就是Servlet Listner,而取到ApplicationContext的地方是我们的DispatcherServlet。
仔
细想一下,其实Web服务器并没有什么了不起的地方,它只是一个Java程序,它只是会在启动的时候去ClassLoad某些指定文件夹下的lib或
classes,它会读某个在WEB-INF下面一个叫做web.xml的配置文件,再做一些初始化工作。Servlet
Listener就是这个初始化工作的重要一步,服务器会读出web.xml里配置好的所有listner,然后调用每个Listner的
contextInitialized方法(它还会去调每个Servlet的init方法,不过把初始化方法写在Listner里才是天经地义的)。哈
哈,这也正是Spring
MVC创建ApplicationContext的最好时机,当我们在web.xml里配置好ContextLoaderListener的时
候,Spring就完成了ApplicationContext的创建过程,如果有人想研究源代码的话可以去看一下,不过这个创建过程并不象想象中的那么
有趣,只是通过Class.forName和BeanUtils.instantiateClass创建出一个
WebApplicationContext,然后再读了一下IOC容器的配置文件。
接下来的一个问题是我们要把创建的
ApplicationContext放在哪里?答案是ServletContext,其实没必须对ServletContext进行深究,它只是可以一
个可以全局存放Web应用程序的场所,我们只要想象成一个全局的HashMap就可以了,我们可以要把它put进去,就可以在Servlet或其它地方把
它get出来。
Web服务器还要干的一件事件当然是在某个request到来时,它会启动一个单独的线程(这也是为何
Webwork可以把Context放到ThreadLocal里的原因),根据web.xml里的配置和request的URI匹配去执行相应的
Servlet。由于Servlet可以很轻松地读到ServletContext,当然也可以很轻松地读到ApplicationContext啦。接
下来的事情就比想象中要简单啦,经过一些准备工作之后ApplicationContext中的URLMapping里配置好的某个
Controller,执行一下再rend某个view就可以了。其实struts或webwork2的执行过程也是如此,所以MVC
framwork分析透了其实真没什么了不起,远比O/R
Mapping或其它的framework简单。虽然MVC的执行过程如此简单,但是我们还需要了解一些细节上的事件,所以让我们下次来讨论一下
Spring MVC framework的执行过程吧。
Spring MVC framework深入分析之三--执行过程
leopallas | 10 九月, 2005 22:24
其实每个MVC framework
的执行过程都是大同小异的,当个request
过来时,它都通过一个Servlet
来响应request
,再根据request
的路径名和配置将这个request dispatch
给一个Controller
执行,最后将之返回配置文件里对应的页面。在Spring MVC
里,这个Servlet
的名字叫DispatchServlet
。稍看一下它的源码会发现这是一很简单的类。
下面是DispatchServlet的类图:
简单吧,这是典型的Template Method模式。每个类都会完成一些自己的本职工作,把不属于自己的工作延迟到子类来完成。这些类的子职责在下面会有分析。其实整个SpringFramework用的最多的模式就是Template Method(Strategy也挺多,呵呵),也许任何Framework用的最多的都是Template Method模式。Why?看看Expert One on One J2EE Design and Development吧,至少Template Method和Strategy模式的分析这本书甚至比Head first Design Pattern还好。
我们先来看DispatchServlet的初始化执行过程分析吧:
我们知道每个Servlet在Web服务器启动时都会有一个初始化的机会,这就是Servlet的init过程,这是配置Servlet的最好机会。我们可以在这个阶段干些啥事情呢?
1、把初始化的那些init-param读到Servlet的属性里。我们知道Servlet的init-param是放在ServletConfig里的,我们可以用循环去取这些属性。但是每次都这么干实在太累了,干吗不把在Servlet里增加几个property,再这些init-param直放到Servlet的property里呢?呵呵,以后那些初始参数都可以直接拿来用啦,真方便。DispatchServlet的一个祖先类叫做HttpServletBean就是专门干这个的。以后假如我们要写自己的Servlet也可以直接继承HttpServletBean这个类,这样读ServletConfig的操作都省掉了,哈哈!
2、从ServletContext里取出ApplicationContext,并扩展成自己的ApplicationContext.
在ApplicationContext之谜里我们已经提到我们可以用Servlet Listner执行的机会把ApplicationContext放到ServletContext中去。但是不是直接拿这个ApplicationContext就足够了呢?no。我们先问一个简单的问题吧:在Spring MVC里我们是不是只能配置一个Servlet呢?Struts就是那么干的,所以在Struts里所有的request过来都会交给一个Servlet去处理。但是在Spring MVC里,我们却可以有好多个Servlet!它们可以处理不同类型的request,而且更重要的是它们的ApplicationContext不是相同的,它们共享了一个父ApplicationContext,也就是从ServletContext里取出来的那个,但是它们却会根据自己的配置作扩展,形成这个Servlet特有的ApplicationContext。这个子的ApplicationContext里有自己的namespace,也就是将一个叫做(假如servlet名称叫xiecc) xiecc-servlet.xml的配置文件读进来,行成一个自己的ServletContext。所以这些过程全是在DispatchSevlet的一个父类FrameworkServlet里干的。
3、初始化接来要干的一件最重要的事情是初始DispatchServlet里的接口。如果用IOC容器的角度来说,其实是将ApplicationContext里定义好的接口注入到一个叫做DispatchServlet的bean里,只不过这个注入过程是手动的。注入的代码大致如果下(部分角色的含义和作用以后会解释):
initMultipartResolver();
initLocaleResolver();
initThemeResolver();
initHandlerMappings();
initHandlerAdapters();
initHandlerExceptionResolvers();
initViewResolvers();
每个init方法其实是属性设置的过程,因为我们可以拿到自己的ApplicationContext,所以这一切都变得很轻松啦。比如说multipartResolver,直接用getWebApplicationContext().getBean(MULTIPART_RESOLVER_BEAN_NAME);就能拿到了。
不过很多东西在配置文件里是不需要写的,比如说multipartResolver,如果在xml里取不到,Spring会初始化默认的MultipartResolver。
接下来我们分析一下DispatchServlet怎么处理Request的执行流程吧:
看一下DispatchServlet的源码会发现它出奇的简单。简单的原因是类Template Method的Strategy模式,呵呵,这是我取的一个怪名字。因为DispatchServlet是不负责任何具体的操作的,它将具体的操作都delegate给了相应的接口,这是典型的Strategy模式。但是DispatchServlet里的doDispatch方法却控制了执行流程,所有的request过来,我们都会按这样的一个流程处理,这和Template Method里的由父类控制流程不谋而合。所以它仍然是Strategy模式,只不过主类还多了个控制流程的职责。
所以DispatchServlet本身的doService方法只是负责控制执行流程,而将所有具体的实现细节全都delegate给相应接口,难怪会那么简单。整个流程也异常的简单,我们甚至找不到循环跳转,所有的东西都是一直线下来的,撇开一些细节不说,剩下的就是这以几步了:
序号
|
步骤
|
具体内容
|
Delegate给谁干?
|
1
|
准备工作
|
设置theme, locale之类的东西。看看Request里是不是要上传文件,如果需要就转成MultipartRequest。
|
|
2
|
从request中取到HandlerExecutionChain
|
这个过程其实是提交给配置文件里定义的HandlerMapping 来做。HandlerExecutionChain是一个很有趣的东西,它包括就是我们要执行的Controller和一组interceptor,我们把它想象成一个执行单元就行啦。(细节上略有不同,下面会有分析)
|
HandlerMapping
|
3
|
执行HandlerExecutionChain
|
其实HandlerExecutionChain里已经包括了要执行的一切东西啦,我们只要把它分解开来执行就行啦。这有点象AOP或Webwork的执行方法pre interceptorsàControlleràpostInterceptors。
|
HandlerExecutionChain,它再delegate给一组interceptor和一个Controller
|
4
|
执行完的结果拿去显示,也就是所谓的render
|
通过ViewResolver的转换后,最后我们会delegate给一个View来完成最后的任务
|
ViewResolver
View
|
5
|
Render完后还有interceptor
|
Spring提供了三个interceptor的机会,前两个Controller方法执行前后
|
HandlerExecutionChain里的interceptor
|
OK.是不是写得很乱?我自己都觉得惭愧啦,没办法,只好让我们再回头分析一下我们碰到几个角色吧:
1、HandlerMapping
HandlerMapping这个接口的定义非常简单:
public interface HandlerMapping {
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
不就是根据request的URL path来取得相应的HandlerExecutionChain。
例如:我的URL是http://localhost:8080/blog/xiecc.htm
RequestURL的字符串一截,拿到了”/xiecc.htm”,再去每个HandlerMapping里一查(还记得初始化时我们已经将所有的HandlerMapping都从配置文件里注入进来了吧),假如我们在某个SimpleUrlHandlerMapping里找到了”/xiecc.htm”,我就立刻可以拿到它对应Controller和一组intercetpors了,拿过来组装一下就是一个HandlerExecutionChain啦。
下面是HandlerMapping的类图:
看上去有点麻烦,其实挺简单,具体的类我就不分析啦。它的核心是:it’s all about HashMap。
还记得我们在Spring MVC最常用的HandlerMapping吗?是SimpleUrlHandlerMapping,我们在配置它的时候,最核心的结构就是HashMap,哈哈!东西都在HashMap里,只要通过URL分析找到HashMap的key,比如说”/xiecc.htm”,用个get方法不就啥都取到了。
在Spring的配置文件里我们可以配置多个HandlerMapping,它会一个一个去找到的,直到找到跟URL匹配的那个Controller,要不然就返回null啦。
2、HandlerExecutionChain
我前面说了HandlerExecutionChain就是一个Controller和一组interceptors。这是我们执行一个request最基本的单元啦。
不过现实情况会稍有些出入,HandlerExecutionChain实际上包括的一个Object和一组interceptor。这个Object是Adaptor,它可以是Controller的Adaptor,也可以是其它类的Adaptor。但现实中我们一般用到的都是Controller,因此不详细分析啦,这里用了Adaptor后大大降低了代码的可读性,来换取与Controller非紧耦合的灵活性。至少我现在认为这样做不是太值。
3、Controller
Controller是Spring MVC执行的核心单元,也是程序员需要自完成的重要部分。用过Spring MVC的人应该都对它非常熟悉啦。所以不做太具体的分析。以下是它的类图:
看一下类图就知道啦,这又是Template Method的典型应用。Controller最大的优势也正是利用Template Method,把Controller分解成不同功能的子类。想要把request里的东西populate到一个bean里吗?直接继承SimpleFormController就行啦。想要在Controller里写多个方法吗?用MultiActionController。这些Controller设计得面面俱到,但因为Controller的类层次太多,有的人会觉得烦。呵呵,随个人喜好啦。
不过最核心的是不管这些Controller如何千变万化,它们都实现了统一的Controller接口,这使DispatchAction调它时候根据不需要知道Controller的细节,嗯,the power of interface。
Controller里的数据绑定也是一个值得研究的东西,挺好玩的,不过这次没空写啦。
4、interceptor
Interceptor的接口定义如下:
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
}
具体的我就不展开分析啦,我们只要记住interceptor的三个hook point(在AOP里叫join point,哈哈):Controller执行前,Controller执行后,页面显示完成后。
5、ViewResolver
ViewResolver是一个有趣的角色,它本身完成两个功能:一是完成了View与实际页面名称对应关系的配置,二是View的工厂(这可是标准的工厂模式啊,每个ViewResolver负责生产自己的View)。
以下是ViewResolver接口的定义:
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
用过Spring MVC的人都配置过ViewResolver,因此这里不详细展开。
我将它对属性分成两类:一类是页面文件配置,包括prefix, suffix;另一类是作为view的工厂注入到View里的属性,如ContentType之类的。
以下是ViewResolver的类图:
6、View
View是真正负责显示页面的地方。它的接口如下:
public interface View {
void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
其实这是一个简单任务,给我一个HashMap,再给个页面URI,把这个页面显示出来还不是小Case。不过不同的View显示到页面上的区别还是挺大的,如果是JSP,我只要把HashMap里的东西填到request里,再交给RequestDispatcher来forward一下就行了;如果是Velocity,那就把HashMap里的东西填到Veloticy的Context里,再把模板生成的东西merge到response的writer里就完事了。当然还有pdf或xls的View,我还没空研究它,哪天有兴趣了再看看吧,以下是View的类图:
写完这篇文章后,终于明白了什么叫做眼高手低。本来很希望写得抽象些,最后却发现我写的东西跟一般的流水帐式的源码分析其实区别不太大。呜呜,从具体到抽象很难,再把抽象的东西转化成具体更难,但只有经过这样的一层转换,我们的认识才能有很大的提高,我们的水平才能进步。