Spring MVC的逻辑实现主要在DispatcherServlet中,它是实现servlet接口的实现类。servlet在初始化时会调用init方法,所以DispatcherServlet也不例外。
默认情况下,Servlet在用户第一次请求时才会被servlet容器创建和初始化,而在使用Spring MVC时通常在web.xml定义DispatcherServlet的load-on-startup为1,即让容器(如tomcat)启动时就加载这个Servlet。(注:load-on-startup的详细介绍可参考:http://www.blogjava.net/xzclog/archive/2011/09/29/359789.html)
本文的目的就是要探究DispatcherServlet在init方法中做了哪些初始化动作。研究的源码版本是4.2.2。
在DispatcherServlet类的定义中,并未找到init方法,它一定是继承了父类中的init方法。它的继承结构如下:
我在HttpServletBean里面找到final修饰的init方法,这意味着它的子类只能继承,不能重写。init方法中关键的代码如下(省略了日志、异常处理等非必要信息)。我在分析代码时,本着先总后分的原则,先将程序分为几个主要的步骤,了解这几个步骤大致做了什么,再对重要的步骤深入。
public final void init() throws ServletException {
//1)DispatcherServlet配置的init-param信息存放在当前Servlet对应的ServletConfig对象中,这里把init-param封装到PropertyValues类中;
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//2)将当前这个Servlet类转化为一个BeanWrapper类型的实例,目的是方便使用Spring提供的注入功能,完成对对应属性的注入。之所以说是对应属性,因为DispatcherServlet的父类FrameworkServlet有和init-param同名的成员变量;
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//3)注册自定义的属性编辑器,一旦遇到Resource类型的属性就会使用使用ResourceEditor进行解析;
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//4)空实现
initBeanWrapper(bw);
//5)属性注入
bw.setPropertyValues(pvs, true);
//6)空实现
initServletBean();
}
4)和6)的实现为什么是空的?原因是模板方法模式,1)到6)这六个步骤组成了一个模板方法,其中某些步骤的具体实现可以让子类实现。
HttpServletBean的子类FrameworkServlet实现了步骤6),且以final修饰,核心代码:
protected final void initServletBean() throws ServletException {
//1)创建或刷新WebApplicationContext实例,并对servlet功能所使用的变量进行初始化;
this.webApplicationContext = initWebApplicationContext();
//2)空实现,又是模板方法的应用;
initFrameworkServlet();
}
继续追踪代码到initWebApplicationContext()方法,核心代码:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//如果this.webApplicationContext!=null,可以判定this.webApplicationContext已经通过构造函数初始化;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
//如果这个context(cwac)尚未被刷新过
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//根据web.xml中配置的servlet参数contextAttribute来查找ServletContext中对应的WebApplicationContext;
wac = findWebApplicationContext();
}
//如果通过上面两种方式仍然无法找到WebApplicationContext实例,那么只能新创建一个了;
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
//如果得到的context不是一个支持刷新的ConfigurableApplicationContext或者构造函数中注入的context已经被刷新过了,那么这里手动触发一次onRefresh;
if (!this.refreshEventReceived) {
onRefresh(wac);
}
if (this.publishContext) {
//attrName的值是:org.springframework.web.servlet.FrameworkServlet.CONTEXT.springMvc,将得到的WebApplicationContext实例放到ServletContext中;
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
这个方法的实现还是有些长的,我们拿几个关键点逐一分析。
1. wac = createWebApplicationContext(rootContext);
跟进createWebApplicationContext方法:
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
继续跟进:
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
//contextClass是org.springframework.web.context.support.XmlWebApplicationContext
Class<?> contextClass = getContextClass();
//如果contextClass不是ConfigurableWebApplicationContext的子类
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("...");
}
ConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);
return wac;
}
2. configureAndRefreshWebApplicationContext(wac) ??
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
3. onRefresh(wac);
FrameworkServlet的onRefresh方法又是空实现,DispatcherServlet覆盖了这个方法,具体实现:
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
跟进代码:
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
这个方法里密密麻麻的初始化了很多变量,接下来逐一分析初始化的这些变量到底是干什么的。
1)initMultipartResolver(context)
MultipartResolver主要用来处理文件上传。
private void initMultipartResolver(ApplicationContext context) {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
}
这里直接从ApplicationContext中获取id为multipartResolver,类型为MultipartResolver的Bean,赋给DispatcherServlet的成员变量multipartResolver。为什么取id为multipartResolver的Bean呢?因为Spring默认是没有multipart处理的,如果想用Spring的multipart,需要在web应用上下文中添加multipart解析器,如下:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize">
<value>20971520</value>
<!-- 上传文件大小限制为20M,20*1024*1024 -->
</property>
</bean>
2) initLocaleResolver(context);
LocaleResolver用来解析用户地区。
3) initThemeResolver(context);
ThemeResolver用来解析主题。
4) initHandlerMappings(context);
HandlerMapping的作用:当请求发给DispatcherServlet时,DispatcherServlet会把请求转给HandlerMapping处理,HandlerMapping经过处理后会返回DispatcherServlet一个可用的Controller。
源码:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
//默认情况下,Spring MVC会加载所有实现了HandlerMapping接口的bean;如果期望Spring MVC只加载指定的handlermapping,可以在DispatcherServlet的init-param中将detectAllHandlerMapping设置为false;
if (this.detectAllHandlerMappings) {
Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
//对handlermappings进行排序;
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
//查找name为handlerMapping的bean,作为当前唯一的handlermapping;
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
if (this.handlerMappings == null) {
//DispatcherServlet所在目录的DispatcherServlet.properties中的org.springframework.web.servlet.HandlerMapping来加载handlermapping,值为org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}
5) initHandlerAdapters(context);
HandlerAdapter使用了适配器模式。作用:DispatcherServlet通过handlerMapping获取对应的handler后,会轮询HandlerAdapter,查找能够处理当前Http请求的HandlerAdapter实现,handlerAdapter根据handlerMapping返回的handler类型来选择适当的handlerAdapter实现,从而适配当前的Http请求。
主要代码:
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
// 加载所有实现了HandlerAdapter接口的bean;
Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
// handlerAdapters排序
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
else {
//获取name为handlerAdapter的bean,作为唯一的handlerAdapter;
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
if (this.handlerAdapters == null) {
//从DispatcherServlet所在目录的DispatcherServlet.properties文件的org.springframework.web.servlet.HandlerAdapter加载默认的三个handlerAdapter;
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
}
}
DispatcherServlet所在目录的DispatcherServlet.properties文件的org.springframework.web.servlet.HandlerAdapter默认的三个handlerAdapter是:
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
6) initHandlerExceptionResolvers(context);
可以通过实现HandlerExceptionResolver接口来自定义异常的处理,然后把实现类在Spring Context中声明为bean。
和上面的处理流程大同小异,同样是如果detectAllHandlerExceptionResolvers为true,先加载所有实现了HandlerExceptionResolver接口的bean,否则加载name为handlerExceptionResolver的bean为唯一的handlerExceptionResolvers;如果依然没找到合适的handlerExceptionResolver,就去按照DispatcherServlet.properties里的org.springframework.web.servlet.HandlerExceptionResolver去加载。
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
else {
HandlerExceptionResolver her =
context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
this.handlerExceptionResolvers = Collections.singletonList(her);
}
if (this.handlerExceptionResolvers == null) {
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
}
}
7) initRequestToViewNameTranslator(context);
当Controller处理器方法没有返回一个View对象或者逻辑视图名称,并且在该方法中没有直接往response的输出流里写数据的时候,Spring就会采用约定好的方式提供一个逻辑视图名,这个逻辑视图名是通过RequestToViewNameTranslator接口的getViewName方法来实现的。
8) initViewResolvers(context);
初始化步骤和上面类似。ViewResolver接口定义了resolveViewName方法,根据viewName创建合适类型的View实现。
9) initFlashMapManager(context);
Spring MVC Flash Attributes提供了请求存储功能,可供其他请求使用,在使用重定向时很必要,例如Post/Redirect/Get模式。Flash attributes在重定向之前暂存以便重定向之后还能使用,并立即删除。
Spring MVC有两个主要的抽象来支持flash attributes,FlashMap用于保持flash attributes,而FlashMapManager用于存储、检索、管理FlashMap实例。
private void initFlashMapManager(ApplicationContext context) {
this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME,FlashMapManager.class);
}
Spring MVC Flash Attributes可参考:http://www.open-open.com/lib/view/open1397266120028.html