已经好久没有写Spring MVC深入分析了,心中不免有些愧意。也许可以用做项目、写论文、看书、暑假出去玩等理由作为借口,但是我知道这些都抵不过一个字——懒!呵呵,不过仔细想想,其实这一章的Spring MVC深入分析迟迟未提笔的重要原因是这一章太难写。有时候写抽象的东西会比写具体的东西容易,因此抽象的东西总是会停留在很高的高度,只要心里有想法就可以尽情地释放出来,所以第一章的Spring MVC深入分析我只用了一个下午多就洋洋洒洒地写完了。而写具体的东西却非常容易陷入记流水帐的泥潭,源码分析更是尤为甚之。
假如去街上找几本Linux源码分析的书,几乎每一本都是源码加注释的方式,哪怕是咱浙大出的那套经典的Linux宝书。也许这样的源码分析会有一定的帮助,尤其是象Linux那么多的代码,也许这样的流水帐式写法是比较现实的。但是当我看了JUnit A Cook’s Tour 这样的文章后,我才会发现真正的源码分析不是那么写的,高手能把具体的东西抽象出来,再将这个抽象的思想进行抽丝拨茧式的分析,可以使别人一下子看到了关键,并马上明白了作者的思路和设计思想。
从具体到抽象正是高手与非高手的重要的区别,一般人的思维总是从具体到具体,最典型的就是看了别人的源码,还不知道怎么回事,就copy过来用。流水帐式的源码分析也是典型的从具体到具体的例子,他没有明白整个系统的设计思想,只是针对具体的每一行代码记下这一行干了什么东西。我不是高手,但是我决定开始装高手,学习高手的想法。假装久了,也便成了高手,呵呵!
写了一堆闲话,该言归正传啦。其实每个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