1.1.1 派遣器Servlet及其父类
上节我们介绍了Servlet容易规范中的HTTP Servlet的实现,它对于各种HTTP方法使用了占位符的实现,这些占位符方法需要子类进一步重写,这一小节中我们将讨论,派遣器Servet是如何实现这些占位符方法来完成Spring Web MVC的工作流的。下图是派遣器Servlet完整的实现体系,
图表 4‑4
从上面实现类图可以看到,继承自HTTP Servlet的直接子类就是Http Servlet Bean。这个类的唯一功能就是把Servlet配置的参数作为一个Bean的属性对Servlet的属性字段进行自动的初始化。(Spring的派遣器Servlet会默认加载一个子环境,这个子环境的位置可以用Spring的初始化参数指定,就是这个功能实现的。)这些属性需要有getter和setter方法。
上面这个特点是通过重写通用Servlet的init()方法提供实现的,如下代码注释,
从上图可以看出,HTTP Servlet Bean初始化自己特殊的资源以后,留下了另外一个占位符方法initServletBean(),这个方法提供子类初始化的机会。
在这个类体系结构中的下一个实现类是框架Servlet, 框架Servlet提供的主要功能就是加载一个Web应用程序环境,这是通过实现父类的占位符方法initServletBean()实现的。并且重写HTTP Servlet中的占位符方法,派遣HTTP请求到统一的Spring Web MVC的控制器方法,进而派遣器Servlet派遣这个HTTP请求到不同的处理器进行处理和响应。
首先,我们分析框架Servlet是如何加载Web应用程序环境的,如下图流程所示,
图表 4‑5
从上图可以看出,框架Servlet试图去查找一个专用的根环境,但是,如果这个专用的根环境不存在,这个Servlet则会查找共享的根环境,使用共享的根环境是我们常见的一种配置。如下程序注解,
我们可以看出框架Servlet初始化后,定义了初始化占位符方法initFrameworkServlet(),子类可以实现这个占位符方法进行初始化,然而,它还定义了另外的初始化的占位符方法onRefresh(), 这个方法是在Web应用程序环境创建时或者刷新时调用的。派遣器Servlet通过重写这个占位符方法进行查找或者初始化Spring Web MVC所需要的各种组件。使用onRefresh()初始化的好处就是可以使Spring Web MVC的组件可以动态的重新加载。
下面是框架Serlvet的初始化全过程,如下图所示,
图表 4‑6
派遣器Servlet通过监听事件得知Servlet的Web应用程序环境初始化或者刷新后,首先在加载的Web应用程序环境(包括主环境和子环境)中查找是不是已经注册了相应的组件,如果查找到注册的组件,就会使用这些组件。如果没有查找到已经注册的相应的组件,派遣器Servlet将会加载缺省的配置策略,这些缺省的配置策略保存在一个属性文件里,这个属性文件和派遣器Servlet在同一个目录里,文件名是DispatcherServlet.properties,通过读取不同组件配置的实现类名,实例化并且初始化这些组件的实现。
Spring Web MVC的组件通过数量分为可选组件,单值组件和多值组件。
· 可选组件是在整个流程里可能需要也可能不需要。例如,MultipartResolver。
· 单值组件是在整个流程里只需要一个这样的组件。例如,ThemeResolver, LocaleResolver和RequestToViewNameTranslator。
· 多值组件是在整个流程里可以配置多个这样的实现组件。运行时,轮询查找哪个组件支持当前的HTTP请求,如果找到一个支持的组件则使用这个组件进行处理。
以下方法initStrategies()是在Web应用程序环境初始化或者刷新的时候调用的,如上图所示,这个方法加载了所有的Spring Web MVC所需要的组件。如下代码注释,
对于可选组件,请看如下代码注释,
对于单值组件,请看如下代码注释,
initThemeResolver()和initRequestToViewNameTranslator()同样是对单值组件进行初始化,他们的实现和initLocaleResolver()具有相同的实现,这里将不进行代码注解。
下面是对多值组件进行初始化的代码注释,
initHandlerAdapters(), initHandlerExceptionResolvers()和initViewResolvers ()同样是对多值组件进行初始化,他们和initHandlerMappings()具有相同的实现,这里将不进行代码注解。
下面的两个代码注释阐述了,缺省的配置策略是如何进行加载的。
上面我们讨论了派遣器Servlet以及父类是如何进行初始化的。从HTTP Servlet Bean,框架Servlet到派遣器Servlet逐层的初始化,每个层次的初始化完成一个特定的功能。当派遣器Servlet初始化完毕后,所有的Spring Web MVC的组件都初始化完毕并且准备进行对HTTP请求进行服务。
接下来,如果一个HTTP请求被派遣到派遣器Servlet,那么派遣器Servlet就开始了真正的Spring Web MVC的工作流,这是一个复杂而又清晰的流程。下面我们将深入分析这个流程。
在上一节中,我们通过研究Servlet规范中的HTTP Servlet的实现得知,HTTP Servlet已经根据HTTP请求所指定的HTTP方法将不同的HTTP请求分发到不同的占位符方法去处理,这些占位符方法是,doGet(), doPost(), doPut(), doDelete()。doHead()方法是通过doGet()方法实现,子类通常是不需要改写的。doOptions()和doTrace()方法的实现基本是不变的,子类通常条件下也不需要改写。
在本书开始章节里介绍了Web MVC模型的组成,我们知道控制器层是Web MVC中不可缺少的一部分,所以,在Spring Web MVC的实现中,改写了这些占位符方法,把HTTP请求重新统一的派遣分发到派遣器Servlet的控制器方法,由框架Servlet中的handleRequest()方法统一进行处理,如下图所示,
图表 4‑7
在框架Servlet中可以配置是否将OPTIONS和TRACE方法派遣到Spring Web MVC的控制流中,通常的Spring Web MVC是不需要派遣和重新实现这两个操作的行为的。请看doGet()的代码注释,
doPost(), doPut(), doDelete()具有相同的实现。也就是说,doGet(), doPost, doPut, doDelete()把HTTP的 GET, POST, PUT和DELETE请求统一的分发到Spring Web MVC的控制器方法进行处理。这里不再做代码注释。
下面是doOptions()的代码注释。
doTrace()的实现和doOptions()的实现是相似的,这里不再做代码注释。
流程走到这里,我们看到所有的HTTP GET, POST, PUT, DELETE甚至OPTIONS和TRACE请求都被统一传递到processRequest()方法进行统一的处理,下面的代码注释了这些请求是如何进一步被Spring Web MVC处理的。
框架Servlet准备好请求环境,并且把请求以及请求属性保存在线程局部存储后,则把控制流传递给派遣器Servlet,这样Spring Web MVC的任何一个角落都能访问到请求以及请求属性。
下面的代码注释用来分析派遣器Servlet如何派遣HTTP请求的。请注意,派遣器Servlet在派遣之前,保存了请求的属性信息,在服务过后恢复了这些信息。
程序执行到这里,Spring Web MVC的工作流正式开始,这个工作流利用前面加载的各个Spring Web MVC的组件协调工作,开始派遣HTTP请求,处理HTTP请求,返回HTTP响应等等,具体步骤如下图,
图表 4‑8
这个活动图精确的阐述了,派遣器Servlet是如果通过初始化的Spring Web MVC的各个组件去处理一个HTTP请求的。我们可以看到,这个过程主要分为2个阶段完成的,
1. 通过映射处理器查找得到处理器对象,在通过支持的处理器对象调用得到的处理器,在调用处理器之前和之后都对应用在这个处理器的拦截器进行了调用。给客户化的处理器机会进行初始化或者析构资源。
2. 解析视图并且显示视图,发送HTTP响应。
上面的2个阶段描述了处理的整体流程,这些流程针对不同的技术有不同的实现。例如,在第1步处理器处理的实现上,有基于简单的Spring控制器的实现,也有基于注解的控制器的实现,还有用于实现远程HTTP调用的实现。对于视图解析和显示,也有不同的实现,例如,基于JSP页面的实现,基于Tiles的实现,基于报表的实现等等。我们将在后面的章节详细讨论这些实现的架构和流程。如下代码注释,