前言 我们打开springMVC工程的web.xml,我们可以发现这样一行配置
1 2 3 <listener > <listener-class > org.springframework.web.context.ContextLoaderListener</listener-class > </listener >
这个监听器就是用来初始化spring容器的。
springMVC启动过程 摘自https://segmentfault.com/q/1010000000210417
首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;
其次,在web.xml中会提供有contextLoaderListener。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEB APPLICATIONCONTEXT ATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是mlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。
从这段话中我们可以知道ContextLoaderListener的作用。ContextLoaderListener主要实现了ServletContextListener接口中的两个函数contextInitialized和contextDestroyed,分别在web容器启动时执行和容器销毁时执行。
类图 我们可以很容易地画出ContextLoaderListener类的结构。
简单地先说一下这个类图。其中ContextLoaderListener实现了ServletContextListener,而真正的具体实现方式确实交给ContextLoader来完成的。
EventListener接口 1 2 public interface EventListener {}
我们可以看到EventListener接口中没有任何东西。那么很显然EventListener只是一个标记接口,就类似于Serializable一样。所有的时间监听器接口都必须扩展该接口。
ServletContextListener接口 1 2 3 4 5 6 7 8 public interface ServletContextListener extends EventListener { public void contextInitialized ( ServletContextEvent sce ) ; public void contextDestroyed ( ServletContextEvent sce ) ; }
ContextLoaderListener类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public ContextLoaderListener () { } public ContextLoaderListener (WebApplicationContext context) { super (context); } @Override public void contextInitialized (ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } @Override public void contextDestroyed (ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } }
此处印证了前面类图那处所说的,最终真正的实现是由ContextLoader来做的。
ContextLoader类 首先从ContextLoaderListener类中contextInitialized调用的initWebApplicationContext方法开始看起。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public WebApplicationContext initWebApplicationContext (ServletContext servletContext) { if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null ) { throw new IllegalStateException( "Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ContextLoader* definitions in your web.xml!" ); } Log logger = LogFactory.getLog(ContextLoader.class); servletContext.log("Initializing Spring root WebApplicationContext" ); if (logger.isInfoEnabled()) { logger.info("Root WebApplicationContext: initialization started" ); } long startTime = System.currentTimeMillis(); try { if (this .context == null ) { this .context = createWebApplicationContext(servletContext); } if (this .context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this .context; if (!cwac.isActive()) { if (cwac.getParent() == null ) { ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } configureAndRefreshWebApplicationContext(cwac, servletContext); } } servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this .context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this .context; } else if (ccl != null ) { currentContextPerThread.put(ccl, this .context); } if (logger.isDebugEnabled()) { logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]" ); } if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms" ); } return this .context; } catch (RuntimeException ex) { logger.error("Context initialization failed" , ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } catch (Error err) { logger.error("Context initialization failed" , err); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); throw err; } }
我们来看看其中的createWebApplicationContext这一步,
1 2 3 4 5 6 7 8 9 10 11 protected WebApplicationContext createWebApplicationContext (ServletContext sc) { Class<?> contextClass = determineContextClass(sc); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]" ); } return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 protected Class<?> determineContextClass(ServletContext servletContext) { String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null ) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load custom context class [" + contextClassName + "]" , ex); } } else { contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load default context class [" + contextClassName + "]" , ex); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties" ;private static final Properties defaultStrategies;static { try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); } }
1 2 3 org.springframework.web.context.WebApplicationContext =org.springframework.web.context.support.XmlWebApplicationContext
这样子的话,我们梳理一下整个流程:
判断ContextLoader是否存在冲突
创建WebApplicationContext
设置WebApplicationContext的父上下文
初始化WebApplicationContext
向servletContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性设置WebApplicationContext,以便于可以直接从servletContext中进行访问
本地实例变量存储context