本文探讨Spring MVC中DispatcherServlet是如何初始化的,DispatcherServlet初始化指的是init()生命周期方法被执行,而不是DispatcherServlet被实例化的过程。
DispatcherServlet类
DispatcherServlet的类层次如下图所示
不管DispatcherServlet被如何包装,它本质上是一个servlet,servlet的生命周期是init -> service -> destroy,因此本文从init()方法入手分析DispatcherServlet的初始化过程。(如果你对servlet不熟悉,我建议你看一下这篇入门指南:Java Servlet完全教程)
init()方法
DispatcherServlet的init()方法在父类HttpServletBean中定义,其代码如下所示:
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
- ServletConfigPropertyValues用于解析web.xml定义中<servlet>元素的子元素<init-param>中的参数值。
若<init-param>元素如下,则ServletConfigPropertyValues就会拥有这些参数<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/dispatcherServlet.xml</param-value> </init-param> <load-on-startup>-1</load-on-startup> </servlet>
- BeanWrapper把DispatcherServlet当做一个Bean去处理,这也是HttpServletBean类名的含义。bw.setPropertyValues(pvs, true) 将上一步解析的servlet初始化参数值绑定到DispatcherServlet对应的字段上;
- init()方法是一个模板方法,initBeanWrapper和initServletBean两个方法由子类去实现。
initServletBean()方法
DispatcherServlet的initServletBean()方法在父类FrameworkServlet中定义,它调用initWebApplicationContext方法初始化DispatcherServlet自己的应用上下文:
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
// 省略一些代码
}
protected void initFrameworkServlet() throws ServletException {
}
初始化servlet应用上下文
initWebApplicationContext方法负责初始化DispatcherServlet自己的应用上下文,其代码如下所示:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
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;
}
该方法的概要流程如下:
- 获得ContextLoaderListener创建的根应用上下文;
- 为DispatcherServlet创建自己的应用上下文;
- 刷新DispatcherServlet自己的应用上下文。
获得根应用上下文
利用WebApplicationContextUtils类的getWebApplicationContext静态方法取得根应用上下文,相关代码如下:
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
Assert.notNull(sc, "ServletContext must not be null");
Object attr = sc.getAttribute(attrName);
if (attr == null) {
return null;
}
if (attr instanceof RuntimeException) {
throw (RuntimeException) attr;
}
if (attr instanceof Error) {
throw (Error) attr;
}
if (attr instanceof Exception) {
throw new IllegalStateException((Exception) attr);
}
if (!(attr instanceof WebApplicationContext)) {
throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
}
return (WebApplicationContext) attr;
}
前面文章指出根应用上下文已经通过ContextLoaderListener被容器初始化,其类型默认是XmlWebApplicationContext类,启动过程中会将WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE(即org.springframework.web.context.WebApplicationContext.ROOT)和根应用上下文通过ServletContext.setAttribute设置到应用的ServletContext。
创建DispatcherServlet的应用上下文
- 若this.webApplicationContext不为null,则说明DispatcherServlet在实例化期间已经被注入了应用上下文。这种情况发生在Spring Boot应用启动时,由于父类FrameworkServlet实现了ApplicationContextAware接口,所以setApplicationContext回调函数被调用时将字段webApplicationContext设置为根应用上下文,注意这并不是在init()初始化方法中完成的,而是在实例化DispatcherServlet的过程中完成的。
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
this.webApplicationContext = (WebApplicationContext) applicationContext;
this.webApplicationContextInjected = true;
}
}
- 若this.webApplicationContext为null,则说明DispatcherServlet在实例化期间没有被注入应用上下文。首先通过findWebApplicationContext方法尝试寻找先前创建的应用上下文。
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
public String getContextAttribute() {
return this.contextAttribute;
}
该方法从ServletContext的属性中找到该servlet初始化属性(<init-param>元素)contextAttribute的值对应的应用上下文,若没有找到则报错。
- 找到的WebApplicationContext是其他servlet初始化时设置到ServletContext属性中的,具体是由initWebApplicationContext方法最后几行做的,其代码如下。
String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac);
可见设置的键值是FrameworkServlet.class.getName() + ".CONTEXT."加上servlet的<servlet-name>值。public String getServletContextAttributeName() { return SERVLET_CONTEXT_PREFIX + getServletName(); } public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
- 以下面的web.xml片段为例,MyServlet会被容器在启动的时候初始化,而dispatcher则是延迟初始化,它们均是DispatcherServlet类型。MyServlet初始化时会在ServletContext中设置以org.springframework.web.servlet.FrameworkServlet.CONTEXT.MyServlet为键,XmlWebApplicationContext对象为值的属性,当dispatcher初始化时,其contextAttribute值恰是由MyServlet初始化应用上下文时设置的键,因此dispatcher初始化时应用上下文就是MyServlet初始化的应用上下文(虽然从源码分析如此但笔者并未在Spring MVC的文档里找到与contextAttribute有关的资料)。
<servlet> <servlet-name>MyServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/dispatcherServlet.xml</param-value> </init-param> <load-on-startup>0</load-on-startup> </servlet> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextAttribute</param-name> <param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.MyServlet</param-value> </init-param> <load-on-startup>-1</load-on-startup> </servlet>
- 若第2步没有找到之前初始化的应用上下文那么就需要通过createWebApplicationContext方法为DispatcherServlet创建一个以根应用上下文为父的应用上下文。这个应用上下文的类型是由父类FrameworkServlet的contextClass字段指定的,可以在web.xml中配置,默认是XmlWebApplicationContext类型,可以参见DispatcherServlet配置文档。
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);
return wac;
}
具体的创建过程如下:
- 实例化contextClass指定类型的应用上下文,同时将根应用上下文设置为它的父上下文,并将<servlet>的contextConfigLocation初始化参数值设置到对应属性;
- 从代码中抛出异常的条件看contextClass属性指定的类必须实现ConfigurableWebApplicationContext接口,而不是文档说明的WebApplicationContext接口,对此问题笔者咨询了Spring作者,见SPR-17414;
- configureAndRefreshWebApplicationContext方法先进一步为DispatcherServlet自己的应用上下文设置了属性,然后调用了各ApplicationContextInitializer实现类的回调函数,最后做了刷新操作实例化各单例bean,其代码如下所示:
applyInitializers方法执行各ApplicationContextInitializer的initialize回调函数,这里的ApplicationContextInitializer分为两种:protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default value // -> assign a more useful id based on available information if (this.contextId != null) { wac.setId(this.contextId); } else { // Generate default id... 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())); // The wac environment's #initPropertySources will be called in any case when the context // is refreshed; do it eagerly here to ensure servlet property sources are in place for // use in any post-processing or initialization that occurs below prior to #refresh ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } postProcessWebApplicationContext(wac); applyInitializers(wac); wac.refresh(); } protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) { } protected void applyInitializers(ConfigurableApplicationContext wac) { String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM); if (globalClassNames != null) { for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) { this.contextInitializers.add(loadInitializer(className, wac)); } } if (this.contextInitializerClasses != null) { for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) { this.contextInitializers.add(loadInitializer(className, wac)); } } AnnotationAwareOrderComparator.sort(this.contextInitializers); for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) { initializer.initialize(wac); } }
- 全局ApplicationContextInitializer类由部署描述符中名为globalInitializerClasses的<context-param>初始化参数指定;
- DispatcherServlet自己的ApplicationContextInitializer类由部署描述符中<servlet>元素内名为contextInitializerClasses的<init-param>初始化参数指定;
- 这两个参数的值都是由各个类名组成的以逗号、分号或空白符分隔的字符串。
-
最后的疑问是部署描述符web.xml中的contextClass等参数是如何被绑定到FrameworkServlet类或DispatcherServlet类的对应字段的呢?这是由上文提到的init()方法中ServletConfigPropertyValues和BeanWrapper完成的。
contextClass.png
刷新DispatcherServlet的应用上下文
onRefresh方法刷新DispatcherServlet自己的应用上下文,DispatcherServlet类重写了父类FrameworkServlet的onRefresh方法,该方法调用initStrategies()方法实例化MultipartResolver、LocaleResolver、HandlerMapping、HandlerAdapter和ViewResolver等组件。
@Override
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);
}
以实例化HandlerMapping的initHandlerMappings方法为例,其代码如下:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
- handlerMappings是DispatcherServlet的一个List<HandlerMapping>;
- detectAllHandlerMappings是DispatcherServlet的一个布尔值属性,表示是否要发现所有的HandlerMapping,若为true则从DispatchServlet自己的应用上下文和根应用上下文获得所有已实例化的HandlerMapping单例,否则只获取名为handlerMapping的HandlerMapping单例;
- 若不存在已实例化的HandlerMapping,那么用默认策略实例化HandlerMapping。
用getDefaultStrategies方法获取默认策略:
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<>(classNames.length);
for (String className : classNames) {
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
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(
"Unresolvable class definition for DispatcherServlet's default strategy class [" +
className + "] for interface [" + key + "]", err);
}
}
return strategies;
}
else {
return new LinkedList<>();
}
}
- defaultStrategies是Properties类型的静态变量,保存了策略名到默认工厂实现类的映射关系,它被DispatcherServlet的静态代码块所填充。默认的策略定义在spring-webmvc包下的DispatcherServlet.properties文件中,该文件内容如下:
# Default implementation classes for DispatcherServlet's strategy interfaces. # Used as fallback when no matching beans are found in the DispatcherServlet context. # Not meant to be customized by application developers. org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
- 以HandlerMapping为例,获取HandlerMapping默认实现的调用是getDefaultStrategies(context, HandlerMapping.class),接着会查找以HandlerMapping.class.getName()为键的值,从文件中可以看到是org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping和org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping两个工厂实现类;
- 其他组件的策略同理,在此不再赘述。
总结
至此,本文完成了对DispatcherServlet的init方法的分析,它已准备好提供服务了,对请求处理的分析请看后续文章。