Spring MVC framework深入分析之二--ApplicationContext之谜

Spring MVC framework深入分析之二–ApplicationContext之谜

假如我们在写一个基于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的类图:

《Spring MVC framework深入分析之二--ApplicationContext之谜》

简单吧,这是典型的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的类图:

《Spring MVC framework深入分析之二--ApplicationContext之谜》

看上去有点麻烦,其实挺简单,具体的类我就不分析啦。它的核心是: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的人应该都对它非常熟悉啦。所以不做太具体的分析。以下是它的类图:

《Spring MVC framework深入分析之二--ApplicationContext之谜》

看一下类图就知道啦,这又是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的类图:

《Spring MVC framework深入分析之二--ApplicationContext之谜》

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

《Spring MVC framework深入分析之二--ApplicationContext之谜》

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

    原文作者:Spring MVC
    原文地址: https://blog.csdn.net/fishineye/article/details/7792057
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞