分享

Spring MVC framework深入分析之三--执行过程

 漂在北方的狼 2007-02-28
其实每个MVC framework的执行过程都是大同小异的,当个request过来时,它都通过一个Servlet来响应request,再根据request的路径名和配置将这个request dispatch给一个Controller执行,最后将之返回配置文件里对应的页面。在Spring MVC里,这个Servlet的名字叫DispatchServlet。稍看一下它的源码会发现这是一很简单的类。

下面是DispatchServlet的类图:

servlet类图

简单吧,这是典型的Template Method模式。每个类都会完成一些自己的本职工作,把不属于自己的工作延迟到子类来完成。这些类的子职责在下面会有分析。其实整个SpringFramework用的最多的模式就是Template MethodStrategy也挺多,呵呵),也许任何Framework用的最多的都是Template Method模式。Why?看看Expert One on One J2EE Design and Development至少Template MethodStrategy模式的分析这本书甚至比Head first Design Pattern还好。

我们先来看DispatchServlet的初始化执行过程分析吧:

我们知道每个ServletWeb服务器启动时都会有一个初始化的机会,这就是Servletinit过程,这是配置Servlet的最好机会。我们可以在这个阶段干些啥事情呢?

1、把初始化的那些init-param读到Servlet的属性里。我们知道Servletinit-param是放在ServletConfig里的,我们可以用循环去取这些属性。但是每次都这么干实在太累了,干吗不把在Servlet里增加几个property,再这些init-param直放到Servletproperty里呢?呵呵,以后那些初始参数都可以直接拿来用啦,真方便。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里定义好的接口注入到一个叫做DispatchServletbean里,只不过这个注入过程是手动的。注入的代码大致如果下(部分角色的含义和作用以后会解释):

initMultipartResolver();

initLocaleResolver();

initThemeResolver();

initHandlerMappings();

initHandlerAdapters();

initHandlerExceptionResolvers();

initViewResolvers();

每个init方法其实是属性设置的过程,因为我们可以拿到自己的ApplicationContext,所以这一切都变得很轻松啦。比如说multipartResolver,直接用getWebApplicationContext().getBean(MULTIPART_RESOLVER_BEAN_NAME);就能拿到了。

不过很多东西在配置文件里是不需要写的,比如说multipartResolver,如果在xml里取不到,Spring会初始化默认的MultipartResolver

接下来我们分析一下DispatchServlet怎么处理Request的执行流程吧:

看一下DispatchServlet的源码会发现它出奇的简单。简单的原因是类Template MethodStrategy模式,呵呵,这是我取的一个怪名字。因为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里已经包括了要执行的一切东西啦,我们只要把它分解开来执行就行啦。这有点象AOPWebwork的执行方法pre interceptorsàControlleràpostInterceptors

HandlerExecutionChain,它再delegate给一组interceptor和一个Controller

4

执行完的结果拿去显示,也就是所谓的render

通过ViewResolver的转换后,最后我们会delegate给一个View来完成最后的任务

ViewResolver

View

5

Render完后还有interceptor

Spring提供了三个interceptor的机会,前两个Controller方法执行前后

HandlerExecutionChain里的interceptor

OK.是不是写得很乱?我自己都觉得惭愧啦,没办法,只好让我们再回头分析一下我们碰到几个角色吧:

1HandlerMapping

HandlerMapping这个接口的定义非常简单

public interface HandlerMapping {

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

不就是根据requestURL 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最常用的HandlerMappingSimpleUrlHandlerMapping我们在配置它的时候,最核心的结构就是HashMap,哈哈!东西都在HashMap里,只要通过URL分析找到HashMapkey,比如说”/xiecc.htm”,用个get方法不就啥都取到了。

Spring的配置文件里我们可以配置多个HandlerMapping,它会一个一个去找到的,直到找到跟URL匹配的那个Controller,要不然就返回null啦。

2HandlerExecutionChain

我前面说了HandlerExecutionChain就是一个Controller和一组interceptors。这是我们执行一个request最基本的单元啦。

不过现实情况会稍有些出入,HandlerExecutionChain实际上包括的一个Object和一组interceptor。这个ObjectAdaptor,它可以是ControllerAdaptor,也可以是其它类的Adaptor。但现实中我们一般用到的都是Controller,因此不详细分析啦,这里用了Adaptor后大大降低了代码的可读性,来换取与Controller非紧耦合的灵活性。至少我现在认为这样做不是太值。

3Controller

ControllerSpring 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里的数据绑定也是一个值得研究的东西,挺好玩的,不过这次没空写啦。

4interceptor

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执行后,页面显示完成后。

5ViewResolver

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的类图:

6View

View是真正负责显示页面的地方。它的接口如下:

public interface View {

void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception;

}

其实这是一个简单任务,给我一个HashMap,再给个页面URI,把这个页面显示出来还不是小Case。不过不同的View显示到页面上的区别还是挺大的,如果是JSP,我只要把HashMap里的东西填到request里,再交给RequestDispatcherforward一下就行了;如果是Velocity,那就把HashMap里的东西填到VeloticyContext里,再把模板生成的东西mergeresponsewriter里就完事了。当然还有pdfxlsView,我还没空研究它,哪天有兴趣了再看看吧,以下是View的类图:

写完这篇文章后,终于明白了什么叫做眼高手低。本来很希望写得抽象些,最后却发现我写的东西跟一般的流水帐式的源码分析其实区别不太大。呜呜,从具体到抽象很难,再把抽象的东西转化成具体更难,但只有经过这样的一层转换,我们的认识才能有很大的提高,我们的水平才能进步。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多