派遣器Servlet及其父类
上节我们介绍了Servlet容易规范中的HTTP Servlet的实现,它对于各种HTTP方法使用了占位符的实现,这些占位符方法需要子类进一步重写,这一小节中我们将讨论,派遣器Servet是如何实现这些占位符方法来完成Spring Web MVC的工作流的。下图是派遣器Servlet完整的实现体系,
从上面实现类图可以看到,继承自HTTP Servlet的直接子类就是Http Servlet Bean。这个类的唯一功能就是把Servlet配置的参数作为一个Bean的属性对Servlet的属性字段进行自动的初始化。(Spring的派遣器Servlet会默认加载一个子环境,这个子环境的位置可以用Spring的初始化参数指定,就是这个功能实现的。)这些属性需要有getter和setter方法。
上面这个特点是通过重写通用Servlet的init()方法提供实现的,如下代码注释,
//重写通用Servlet的init()方法占位符进行初始化Servlet Bean,这些初始化信息来自于Serlvet配置的参数,我们通常通过Servlet初始化参数给一个Serlvet指定一个非默认名字的Spring Context的文件路径,就是这里实现的
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
//这个Servlet名字就是从Servlet配置对象中取得的,而Serlvet配置是在通用Servlet初始化阶段保存的
logger.debug("Initializing servlet '" + getServletName() + "'");
}
//设置Servlet初始化参数作为Servlet Bean的属性
try {
//使用Servlet配置的初始化参数创建一个PropertyValues对象,PropertyValues对象是名值对的集合, 子类也可以指定哪些属性是必须的
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//把当前的Servlet当作一个Bean, 把Bean的属性以及属性的存取方法信息放入BeanWrapper对象
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//注册一个可以在资源和路径之间进行转化的客户化编辑器,这些资源是这个Web应用的内部资源,例如,一个文件,一个图片等等
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader));
//提供给子类机会增加更多的客户化的编辑器,或者对BeanWrapper进行更多的初始化
initBeanWrapper(bw);
//把初始化制定的参数值赋值到Servlet的属性中,第二个参数true表明忽略位置属性
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
//给子类一个机会去初始化子类需要的资源,同样是一个占位符方法
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
从上图可以看出,HTTP Servlet Bean初始化自己特殊的资源以后,留下了另外一个占位符方法initServletBean(),这个方法提供子类初始化的机会。
在这个类体系结构中的下一个实现类是框架Servlet, 框架Servlet提供的主要功能就是加载一个Web应用程序环境,这是通过实现父类的占位符方法initServletBean()实现的。并且重写HTTP Servlet中的占位符方法,派遣HTTP请求到统一的Spring Web MVC的控制器方法,进而派遣器Servlet派遣这个HTTP请求到不同的处理器进行处理和响应。
首先,我们分析框架Servlet是如何加载Web应用程序环境的,如下图流程所示,
//重写了Http Servlet Bean的初始化占位符方法initServletBean(),进而初始化框架Serlvet所需的资源,在这里就是Web应用程序环境
@Override
protected final void initServletBean() throws ServletException {
//打印初始化信息到Servlet容易的日志
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
//取得初始化环境的开始时间
long startTime = System.currentTimeMillis();
try {
//初始化Servlet的环境,这个方法的流程见上图
this.webApplicationContext = initWebApplicationContext();
//同样调用一个占位符方法,这个占位符方法给子类机会去初始化子类指定的资源,这是这个方法在派遣器Servlet中并没有覆盖
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
//取得初始化环境的结束时间
long elapsedTime = System.currentTimeMillis() - startTime;
//log初始化Web应用程序环境所需的总体时间
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
protected WebApplicationContext initWebApplicationContext() {
//首先查找是否这个派遣器Servlet有一个专用的根环境,这个根环境是通过一个属性(contextAttribute)作为关键字存储在Servlet环境里的,这个属性可以在Servlet的初始化参数中指定,因为在HTTP Servlet Bean的初始化过程中,初始化参数将被当作为Bean属性进行赋值
WebApplicationContext wac = findWebApplicationContext();
//如果这个Servlet不存在专用的根环境
//通常我们不需要这个环境,因为我们通常使用一个Web监听器进行加载一个默认的共享的根环境
if (wac == null) {
//取得默认的共享的根环境,这个根环境通过关键字ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE保存在Servlet环境对象里
WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
//创建派遣器Servlet的子环境,这个子环境引用得到的主环境,这个猪环境是可选的
wac = createWebApplicationContext(parent);
}
//Web应用程序环境在创建之后,指定了这个类作为Web应用程序环境事件处理的监听器,如果这个Web应用程序环境支持刷新, 这个onRefresh方法应该已经调用,否则,我们需要手工激发初始化事件
//这个刷新方法将被派遣器Servlet重写,它将提取并初始化Spring Web MVC的各个组件。
//框架Servlet初始化为子类准备了初始化占位符方法initFrameworkServlet(), 但是,同时也准备了onRefresh()方法,因为一个ConfigurableApplicationContext是支持动态刷新的,依赖于Web应用程序环境的子类组件应该监听这个方法进行重新初始化,派遣器Servlet就是这样实现的
if (!this.refreshEventReceived) {
onRefresh(wac);
}
//如果设置了发布环境属性,则把这个Web应用程序环境以ServletContextAttributeName的值作为关键字保存到Servlet环境对象里,这个关键字是org.springframework.web.servlet.FrameworkServlet.CONTEXT. + Servlet名称
//这样做可以在其他的Servlet中共享这个Web应用程序环境
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
我们可以看出框架Servlet初始化后,定义了初始化占位符方法initFrameworkServlet(),子类可以实现这个占位符方法进行初始化,然而,它还定义了另外的初始化的占位符方法onRefresh(), 这个方法是在Web应用程序环境创建时或者刷新时调用的。派遣器Servlet通过重写这个占位符方法进行查找或者初始化Spring Web MVC所需要的各种组件。使用onRefresh()初始化的好处就是可以使Spring Web MVC的组件可以动态的重新加载。
下面是框架Serlvet的初始化全过程,如下图所示,
派遣器Servlet通过监听事件得知Servlet的Web应用程序环境初始化或者刷新后,首先在加载的Web应用程序环境(包括主环境和子环境)中查找是不是已经注册了相应的组件,如果查找到注册的组件,就会使用这些组件。如果没有查找到已经注册的相应的组件,派遣器Servlet将会加载缺省的配置策略,这些缺省的配置策略保存在一个属性文件里,这个属性文件和派遣器Servlet在同一个目录里,文件名是DispatcherServlet.properties,通过读取不同组件配置的实现类名,实例化并且初始化这些组件的实现。
Spring Web MVC的组件通过数量分为可选组件,单值组件和多值组件。
(1)可选组件是在整个流程里可能需要也可能不需要。例如,MultipartResolver。
(2)单值组件是在整个流程里只需要一个这样的组件。例如,ThemeResolver, LocaleResolver和RequestToViewNameTranslator。
(3)多值组件是在整个流程里可以配置多个这样的实现组件。运行时,轮询查找哪个组件支持当前的HTTP请求,如果找到一个支持的组件则使用这个组件进行处理。
以下方法initStrategies()是在Web应用程序环境初始化或者刷新的时候调用的,如上图所示,这个方法加载了所有的Spring Web MVC所需要的组件。如下代码注释,
protected void initStrategies(ApplicationContext context) {
//初始化多部请求解析器,没有默认的实现
initMultipartResolver(context);
//初始化地域解析器,默认实现是AcceptHeaderLocaleResolver
initLocaleResolver(context);
//初始化主题解析器,默认实现是FixedThemeResolver
initThemeResolver(context);
//初始化处理器映射,这是个集合, 默认实现是BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping
initHandlerMappings(context);
//初始化处理器适配器,这是个集合,默认实现是HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter和AnnotationMethodHandlerAdapter
initHandlerAdapters(context);
//初始化处理器异常解析器,这是个集合,默认实现是AnnotationMethodHandlerExceptionResolver,ResponseStatusExceptionResolver和DefaultHandlerExceptionResolver
initHandlerExceptionResolvers(context);
//初始化请求到视图名解析器,默认实现是DefaultRequestToViewNameTranslator
initRequestToViewNameTranslator(context);
//初始化视图解析器,这是个集合,默认实现是InternalResourceViewResolver
initViewResolvers(context);
}
对于可选组件,请看如下代码注释,
private void initMultipartResolver(ApplicationContext context) {
try {
//从配置的Web应用程序环境中查找多部请求解析器
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
this.multipartResolver = null;
//如果没有多部请求解析器在Web应用程序环境中注册,忽略这种情况,毕竟不是所有的应用程序都需要使用它,多部请求通常会应用到文件上传的情况
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
"': no multipart request handling provided");
}
}
}
对于单值组件,请看如下代码注释,
private void initLocaleResolver(ApplicationContext context) {
try {
//从配置的Web应用程序环境中查找地域请求解析器
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
//如果没有地域请求解析器在Web应用程序环境中注册,则查找缺省配置的策略,并且根据配置初始化缺省的地域请求解析器,后面将代码注解如何加载默认策略
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
"': using default [" + this.localeResolver + "]");
}
}
}
initThemeResolver()和initRequestToViewNameTranslator()同样是对单值组件进行初始化,他们的实现和initLocaleResolver()具有相同的实现,这里将不进行代码注解。
下面是对多值组件进行初始化的代码注释,
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
//如果配置成为自动检测所有的处理器映射,则在加载的Web应用程序环境中查找所有实现HandlerMapping接口的Bean
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// 根据这些Bean所实现的Order接口进行排序
OrderComparator.sort(this.handlerMappings);
}
}
else {
//如果没有配置成为自动检测所有的处理器映射,则在Web应用程序环境中查找指定名字为”handlerMapping”的Bean作为处理器映射
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
//构造单个Bean的集合
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
//忽略异常,后面将使用对象引用是否为空判断是否查找成功
}
}
if (this.handlerMappings == null) {
//如果仍然没有查找到注册的HanlderMapping的实现,则使用缺省的配置策略加载处理器映射
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
initHandlerAdapters(), initHandlerExceptionResolvers()和initViewResolvers ()同样是对多值组件进行初始化,他们和initHandlerMappings()具有相同的实现,这里将不进行代码注解。
下面的两个代码注释阐述了,缺省的配置策略是如何进行加载的。
protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
//对于单值的组件加载,首先重用了多值的组件的加载方法,然后判断是否只有一个组件配置返回,期待的结果是有且只有一个组件被配置,否则将抛出异常
List<T> strategies = getDefaultStrategies(context, strategyInterface);
if (strategies.size() != 1) {
throw new BeanInitializationException(
"DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
}
return strategies.get(0);
}
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
//取得组件接口的完全的类名,缺省策略是通过组件接口的类名作为关键字存储在属性文件里的
String key = strategyInterface.getName();
//取得以这个接口名为关键字配置的所有实现类的名字,这些类的名字是通过逗号分隔的
String value = defaultStrategies.getProperty(key);
if (value != null) {
//把逗号分隔的类名字符串转化成字符串的数组
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
//对每一个配置的类名字,加载这个类并且初始化这个组件
List<T> strategies = new ArrayList<T>(classNames.length);
for (String className : classNames) {
try {
//通过类的名字加载这个类
Class clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
//在Web应用程序环境中创建这个组件的类的对象
Object strategy = createDefaultStrategy(context, clazz);
//将初始化的组件Bean加入到返回的集合结果中
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
//如果找不到这个类的定义
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
//如果找不到加载的类的依赖类
throw new BeanInitializationException(
"Error loading DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]: problem with class file or dependent class", err);
}
}
return strategies;
}
else {
//如果没有缺省策略配置,则返回一个空列表
return new LinkedList<T>();
}
}
上面我们讨论了派遣器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()方法统一进行处理,如下图所示,
在框架Servlet中可以配置是否将OPTIONS和TRACE方法派遣到Spring Web MVC的控制流中,通常的Spring Web MVC是不需要派遣和重新实现这两个操作的行为的。请看doGet()的代码注释,
//这个方法被定义为final,HTTP GET请求应该派往到Spring Web MVC流程去处理,子类不应该改写这个方法
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//简单的派遣HTTP GET请求到Spring Web MVC的控制流
processRequest(request, response);
}
doPost(), doPut(), doDelete()具有相同的实现。也就是说,doGet(), doPost, doPut, doDelete()把HTTP的 GET, POST, PUT和DELETE请求统一的分发到Spring Web MVC的控制器方法进行处理。这里不再做代码注释。
下面是doOptions()的代码注释。
//Spring Web MVC并不需要对OPTIONS请求进行任何特殊的处理,所以,这个方法是可以被子类改写的,也就是说,改写后不会对Spring Web MVC流程有任何的影响
protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//调用HTTP Servlet中对OPTIONS方法的默认实现
super.doOptions(request, response);
//通过配置可以决定是否将OPTIONS和TRACE方法派遣到Spring Web MVC的控制流中,通常是不需要的
if (this.dispatchOptionsRequest) {
processRequest(request, response);
}
}
doTrace()的实现和doOptions()的实现是相似的,这里不再做代码注释。
流程走到这里,我们看到所有的HTTP GET, POST, PUT, DELETE甚至OPTIONS和TRACE请求都被统一传递到processRequest()方法进行统一的处理,下面的代码注释了这些请求是如何进一步被Spring Web MVC处理的。
//服务前保存线程局部存储的信息,服务后回复这些信息
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//记录处理请求的开始时间
long startTime = System.currentTimeMillis();
//记录产生的异常在finally语句里打印出来
Throwable failureCause = null;
//因为一个线程可能处理不同的请求,这经常发生在forward, include的操作上,所以在处理之前需要保存一些容易被覆盖的信息,请求结束后进行恢复
//保存当前线程局部存储的地域信息,以备处理完这个请求后进行恢复
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//导出当前的请求的地域信息到线程的局部存储,所以尽管你不能得到HTTP Request对象, 在Spring Web MVC流程中的任意位置都可以取得这个地域信息的值
LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);
//保存当前线程局部存储的请求属性,以备处理完这个请求后进行恢复
RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = null;
//如果当前线程局部存储不包含请求属性,或者包含着同样的ServletRequestAttributes的实例,则导出新的请求属性,这个新的请求环境能够连接到request以及session对象的
//如果这个Servlet是第一个组件处理这个请求,previousRequestAttributes一定为空
//对于包含请求的情况,previousRequestAttributes并不是空,而且是类ServletRequestAttributes的一个实例,因为在前一个Servlet组件的处理中建立了一个ServletRequestAttributes的实例, 所以需要创建一个新的请求属性并且保存在环境中,这个新的请求属性是供这个Servlet使用,并且需要保存前一个请求属性的
if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {
//基于当前的请求对象创建一个请求属性对象
requestAttributes = new ServletRequestAttributes(request);
//把新创建的请求属性对象放入线程的局部存储中
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
if (logger.isTraceEnabled()) {
logger.trace("Bound request context to thread: " + request);
}
try {
//开始Spring Web MVC的真正的派遣工作流,这个方法在框架Servlet中定义为抽象方法,在派遣器Servlet中实现
doService(request, response);
}
catch (ServletException ex) {
//保存异常在finally语句里打印log
failureCause = ex;
throw ex;
}
catch (IOException ex) {
//保存异常在finally语句里打印log
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
//保存异常在finally语句里打印log
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
//请求处理完,恢复先前的线程局部存储中的地域信息
LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
//如果在处理HTTP请求前导出了新的请求属性,这里恢复原来的线程局部存储的请求属性
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
requestAttributes.requestCompleted();
}
if (logger.isTraceEnabled()) {
logger.trace("Cleared thread-bound request context: " + request);
}
if (failureCause != null) {
//异常发生时保存异常,在finally语句里打印出来
this.logger.debug("Could not complete request", failureCause);
}
else {
this.logger.debug("Successfully completed request");
}
if (this.publishEvents) {
// 计算这个请求的总处理时间,将时间传递给应用程序环境,注册事件监听器的Bean就会接收到这个事件,可以用作为统计分析
long processingTime = System.currentTimeMillis() - startTime;
this.webApplicationContext.publishEvent(
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause));
}
}
}
框架Servlet准备好请求环境,并且把请求以及请求属性保存在线程局部存储后,则把控制流传递给派遣器Servlet,这样Spring Web MVC的任何一个角落都能访问到请求以及请求属性。
下面的代码注释用来分析派遣器Servlet如何派遣HTTP请求的。请注意,派遣器Servlet在派遣之前,保存了请求的属性信息,在服务过后恢复了这些信息。
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String requestUri = new UrlPathHelper().getRequestUri(request);
logger.debug("DispatcherServlet with name '" + getServletName() + "' processing " + request.getMethod() +
" request for [" + requestUri + "]");
}
//对于一个include请求,除了需要保存和恢复请求环境信息,还需要保存请求属性,待请求处理完毕后,如果其中某一个属性改变了,我们需要恢复这些属性
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
//如果是一个包含请求,则遍历所有请求的属性
logger.debug("Taking snapshot of request attributes before include");
attributesSnapshot = new HashMap<String, Object>();
Enumeration attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
//如果包含请求清除属性打开,默认是打开的,则把所有spring指定的属性进行保存,这些属性的关键字是以org.springframework.web.servlet开头的
if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// 在包含请求的情况下,例如,一个URI /action/process1包含另外一个URI /action/process2,上面的这些属性如果和主请求重复,包含请求结束后,这些属性的值将会被恢复
// 存储Web应用程序环境在request属性里
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
// 存储地域解析器在request属性里
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
// 存储主题解析器在request属性里
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
// 存储主题源在request属性里
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
try {
//开始Spring Web MVC的真正的派遣工作流
doDispatch(request, response);
}
finally {
if (attributesSnapshot != null) {
//恢复保存的Spring指定的请求属性
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
程序执行到这里,Spring Web MVC的工作流正式开始,这个工作流利用前面加载的各个Spring Web MVC的组件协调工作,开始派遣HTTP请求,处理HTTP请求,返回HTTP响应等等,具体步骤如下图,
这个活动图精确的阐述了,派遣器Servlet是如果通过初始化的Spring Web MVC的各个组件去处理一个HTTP请求的。我们可以看到,这个过程主要分为2个阶段完成的,
(1)通过映射处理器查找得到处理器对象,在通过支持的处理器对象调用得到的处理器,在调用处理器之前和之后都对应用在这个处理器的拦截器进行了调用。给客户化的处理器机会进行初始化或者析构资源。
(2) 解析视图并且显示视图,发送HTTP响应。
上面的2个阶段描述了处理的整体流程,这些流程针对不同的技术有不同的实现。例如,在第1步处理器处理的实现上,有基于简单的Spring控制器的实现,也有基于注解的控制器的实现,还有用于实现远程HTTP调用的实现。对于视图解析和显示,也有不同的实现,例如,基于JSP页面的实现,基于Tiles的实现,基于报表的实现等等。我们将在后面的章节详细讨论这些实现的架构和流程。如下代码注释,
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
int interceptorIndex = -1;
try {
ModelAndView mv;
boolean errorView = false;
try {
//如果是HTTP多部请求,则转换并且封装成一个简单的HTTP请求
processedRequest = checkMultipart(request);
// 根据处理器映射的配置,取得处理器执行链对象
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
//如果没有发现任何处理器,发送错误信息
noHandlerFound(processedRequest, response);
return;
}
// 开始调用前置拦截器
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
//依次调用前置拦截器,如果任何一个拦截器返回false,则结束整个流程,反序掉用前面执行过的前置拦截器的所有完成拦截器,然后返回, 停止处理流程。
if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
return;
}
interceptorIndex = i;
}
}
// 查找支持的处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 通过得到的处理器适配器,调用得到的处理器,处理器适配器和处理器类型是成对出现的,因为处理器适配器知道如何调用它所支持的处理器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (mv != null && !mv.hasView()) {
//如果控制器没有返回任何逻辑视图名,则用缺省的请求到视图名翻译器得到视图名
mv.setViewName(getDefaultViewName(request));
}
// 应用后置拦截器
if (interceptors != null) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
//反序调用所有的后置拦截器,因为后置拦截器是用来释放资源的,如果初始化资源时用的是正序,那么起清除资源最好使用反序调用,这样就会很好的解决了资源依赖的问题
interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
}
}
}
catch (ModelAndViewDefiningException ex) {
//这个异常是用来跳过后续的处理过程,直接进入视图解析和显示阶段
logger.debug("ModelAndViewDefiningException encountered", ex);
mv = ex.getModelAndView();
}
catch (Exception ex) {
//如果产生任何没有处理的异常,则调用处理器异常解析器,取得异常情况下的模型和视图对象,然后进入视图解析和显示阶段
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
if (mv != null && !mv.wasCleared()) {
//如果返回了一个视图对象,则解析视图和显示视图
render(mv, processedRequest, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
//如果没有返回了一个视图对象,则不进行试图解析和显示
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
//如果处理成功,调用完成拦截器
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
}
catch (Exception ex) {
//如果处理失败并且产生异常,调用完成拦截器,并且传入产生的异常,进行异常处理
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
catch (Error err) {
ServletException ex = new NestedServletException("Handler processing failed", err);
//如果处理失败并且产生错误,调用完成拦截器,并且传入产生的错误,进行错误处理
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
finally {
//清除Multipart资源
if (processedRequest != request) {
cleanupMultipart(processedRequest);
}
}
}