我们在配置Spring WEB项目的时候,通常会有这样的配置:
1 2 3 4 5 6 7 8 9 10 <listener > <listenerclass > org.springframework.web.context.ContextLoaderListener </listener-class > </listener > <context-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:config/applicationContext.xml</param-value > </context-param >
在部署描述符中指定ContextLoaderListener
,通过这个Listener来读取xml配置,达到启动Spring应用上下文的目的。今天我们来看看ContextLoaderListener
是如何启动Spring容器的。
ContextLoaderListener
启动的流程图如下:
我们来看看ContextLoaderListener
的代码,ContextLoaderListener
主要是实现了ServletContextListener
的启动和销毁方法,具体的逻辑实现在父类ContextLoader
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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()); } }
initWebApplicationContext
函数定义在ContextLoader
上,实例化并初始化WebApplicationContext:
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 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; } }
注意一点,在(1)这个分支处。如果是使用基于xml的配置,那么this.context == null
成立,会调用createWebApplicationContext
来新建WebApplicationContext,默认使用XmlWebApplicationContext
来作为WebApplicationContext实现。也可以使用contextClass
参数来手动指定使用的WebApplicationContext
实现类,举个例子:
1 2 3 4 5 6 7 8 9 10 11 <context-param > <param-name > contextClass</param-name > <param-value > org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value > </context-param > <context-param > <param-name > contextConfigLocation</param-name > <param-value > com.mushan.AppConfig</param-value > </context-param >
如果是基于javaconfig配置,那么在AbstractContextLoaderInitializer
启动时,默认会使用AnnotationConfigWebApplicationContext
来初始化ContextLoaderListener,见代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer { protected void registerContextLoaderListener (ServletContext servletContext) { WebApplicationContext rootAppContext = createRootApplicationContext(); if (rootAppContext != null ) { servletContext.addListener(new ContextLoaderListener(rootAppContext)); } else { logger.debug("No ContextLoaderListener registered, as " + "createRootApplicationContext() did not return an application context" ); } } protected abstract WebApplicationContext createRootApplicationContext () ; ... }
我们一般使用AbstractAnnotationConfigDispatcherServletInitializer
,所以createRootApplicationContext
函数在上面得到了实现:
1 2 3 4 5 6 7 8 9 10 11 protected WebApplicationContext createRootApplicationContext () { Class<?>[] configClasses = getRootConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); rootAppContext.register(configClasses); return rootAppContext; } else { return null ; } }
这里可以看到是用了AnnotationConfigWebApplicationContext
作为实现了。
层次关系示意图:
总结
ContextLoaderListener是Web应用启动Spring应用上下文的入口
基于部署描述符配置,默认使用XmlWebApplicationContext
作为WebApplicationContext
实现类
基于JavaConfig配置,默认使用AnnotationConfigWebApplicationContext
作为WebApplicationContext
实现类
ContextLoaderListener会持有WebApplicationContext
实例,用于销毁
同时ContextLoaderListener会把WebApplicationContext
实例注册到ServletContext中