ContextLoaderListener
Servlet容器实例化ContextLoaderListener
Servlet容器会实例化一个ContextLoaderListener
org.springframework.web.context.ContextLoaderListener
该类继承ContextLoader,实现了ServletContextListener接口,使之具有listener功能
public class ContextLoaderListener extends ContextLoader implements ServletContextListener
ContextLoaderListener 实现了ServletContextListener接口
public interface ServletContextListener extends EventListener { /** * web应用初始化开始的时候,收到通知, 所有的ServletContextListeners都会 * 收到通知,在任何filter或者servlet之前 */ public void contextInitialized(ServletContextEvent sce); /** * ServletContext准备关闭的时候,会调用该方法; * 在contextDestroyed调用前,所有的servlet和filter都已经销毁了 */ public void contextDestroyed(ServletContextEvent sce);}
/** * Initialize the root web application context. */ public void contextInitialized(ServletContextEvent event) { this.contextLoader = createContextLoader(); if (this.contextLoader == null) { this.contextLoader = this; } this.contextLoader.initWebApplicationContext(event.getServletContext()); } ... /** * Close the root web application context. */ public void contextDestroyed(ServletContextEvent event) { if (this.contextLoader != null) { this.contextLoader.closeWebApplicationContext(event.getServletContext()); } ContextCleanupListener.cleanupAttributes(event.getServletContext()); }
- ServletContextListener扩展了ContextLoader,使之有Listener功能
- 在
public void contextInitialized(ServletContextEvent sce);
方法中,获取ServletContext - 在
public void contextDestroyed(ServletContextEvent event);
方法中,释放资源。
流程图描述该过程
Servlet容器加载实例化ContextLoaderListener调用contextInitialized(ServletContextEvent sce)End
contextInitialized方法内部实现
/** * Initialize the root web application context. */ public void contextInitialized(ServletContextEvent event) { //createContextLoader方法已经弃用,返回永远是null this.contextLoader = createContextLoader(); if (this.contextLoader == null) { this.contextLoader = this; } //调用父类initWebApplicationContext方法,传入ServletContext this.contextLoader.initWebApplicationContext(event.getServletContext()); } /** * Create the ContextLoader to use. Can be overridden in subclasses. * @return the new ContextLoader * @deprecated in favor of simply subclassing ContextLoaderListener itself * (which extends ContextLoader, as of Spring 3.0) */ @Deprecated protected ContextLoader createContextLoader() { return null; }
关键代码:
this.contextLoader.initWebApplicationContext(event.getServletContext());
得到信息:
1. 主要初始化任务在initWebApplicationContext中实现.
ContextLoader
1. 初始化ApplicationContext — initWebApplicationContext
关键代码:
1.this.context = createWebApplicationContext(servletContext);
2. servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
3. configureAndRefreshWebApplicationContext(cwac, servletContext);
得到信息:
1. 该代码创建了WebApplicationContext 2. 这个源代码很长(- -) 3. 关键代码在createWebApplicationContext(servletContext);
里面 4.configureAndRefreshWebApplicationContext(cwac, servletContext);
创建之后的配置工作,这个也很重要,以后再说,这里直说创建Context过程。
代码
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 { // Store context in local instance variable, to guarantee that // it is available on ServletContext shutdown. if (this.context == null) { this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; 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 -> // determine parent for root web application context, if any. 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; } }
2. 创建ApplicationContext — createWebApplicationContext
关键代码:
1.Class<?> contextClass = determineContextClass(sc);
2. return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
得到信息:
1. 根据传入的sevletContext,返回使用WebApplicationContext接口的哪一个实现类,默认是XmlWebApplicationContext 2. 使用BeanUtils实例化该类
源代码
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); }
3. 决定使用哪个WebApplicationContext?—determineContextClass
关键代码:
1.String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
2. public static final String CONTEXT_CLASS_PARAM = "contextClass";
3. return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
得到信息:
1. 从Servlet InitParam中获取的contextClassName,决定到底使用WebApplicationContext接口的哪一个实现类。 2. 如果你的web.xml中定义了如下片段,会使用你自己的WebApplicationContext (默认”contextClass”),否则就使用XmlWebApplicationContext 3. 利用反射得到Class
web.xml
contextClass Your ContextClass
代码
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); } } }
总结
配置web.xml
org.springframework.web.context.ContextLoaderListener
- Servlet容器实例化ContextLoaderListener,并通知其调用
initWebApplicationContext
- ContextLoaderListener调用
createWebApplicationContext
根据ServletContext创建出WebApplicationContext - 调用configureAndRefreshWebApplicationContext进行接下来的操作
彻底晕了,后面的还是画个图吧
2:
Spring IOC容器通过ServletContextListener对servlet容器的生命周期监听,从而实现了IOC的启动和销毁。
注意:
1.分析框架代码时,要常使用类继承、调用关系等快捷键,可以更高效的学习,快捷键可以设置成你习惯的按键;
2.本文重在怎么自我分析框架代码,所以对其中解析需自己实际跟踪代码实践方可;
3.spring源代码版本 spring-framework-3.2.1.RELEASE。
预览
javax.servlet.ServletContext,Servlet容器接口。
javax.servlet.ServletContextListener,Servlet容器生命周期监听接口。
org.springframework.web.context.ContextLoaderListener,Spring IOC容器生命周期监听类。
org.springframework.web.context.ContextLoader,Spring IOC容器启动和销毁。
配置-监听ServletContext生命周期
在web.xml中spring配置了对ServletContext生命周期的监听,当Web容器启动和销毁时,触发Spring定义的IOC容器的启动和销毁,具体配置如下:
[html]
- <listener>
- <listener-class>
- org.springframework.web.context.ContextLoaderListener
- </listener-class>
- </listener>
入口-ContextLoaderListener对Spring IOC初始化和销毁
当web容器启动时会触发contextInitialized方法对spring ioc容器进行初始化,销毁时会触发contextDestroyed方法对spring ioc容器进行销毁,ServletContextListener接口如下:
- public void contextInitialized ( ServletContextEvent sce ); // ServletContext启动时触发
- public void contextDestroyed ( ServletContextEvent sce ); // ServletContext销毁时触发
Spring IOC启动
spring ioc容器初始化具体代码如下:
- org.springframework.web.context.ContextLoaderListener
- public void contextInitialized(ServletContextEvent event) {
- this.contextLoader = createContextLoader();
- if (this.contextLoader == null) {
- this.contextLoader = this;
- }
- // 对spring ioc容器进行初始化
- this.contextLoader.initWebApplicationContext(event.getServletContext());
- }
IOC容器的初始化是由ContextLoader类执行:
- org.springframework.web.context.ContextLoader
- public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
- // 是否已经加载IOC容器
- 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 {
- // Store context in local instance variable, to guarantee that
- // it is available on ServletContext shutdown.
- if (this.context == null) {
- this.context = createWebApplicationContext(servletContext);
- }
- if (this.context instanceof ConfigurableWebApplicationContext) {
- ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
- 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 ->
- // determine parent for root web application context, if any.
- ApplicationContext parent = loadParentContext(servletContext);
- cwac.setParent(parent);
- }
- // 此方法是刷新初始化IOC容器的地方
- configureAndRefreshWebApplicationContext(cwac, servletContext);
- }
- }
- // IOC容器加载后,将IOC容器保存于ServletContext容器中
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
- ...
- }
熟悉的refresh()方法的调用:
- org.springframework.web.context.ContextLoader
- protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
- 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
- String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
- if (idParam != null) {
- wac.setId(idParam);
- }
- else {
- // Generate default id...
- if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
- // Servlet <= 2.4: resort to name specified in web.xml, if any.
- wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
- ObjectUtils.getDisplayString(sc.getServletContextName()));
- }
- else {
- wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
- ObjectUtils.getDisplayString(sc.getContextPath()));
- }
- }
- }
- wac.setServletContext(sc);
- String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
- if (initParameter != null) {
- wac.setConfigLocation(initParameter);
- }
- customizeContext(sc, wac);
- // 熟悉的refresh()对Spring IOC容器进行加载,接下来的步骤就是 "自我分析-Spring IOC"文脏中的内容
- wac.refresh();
- }
Spring IOC销毁
对Spring IOC容器和其他Spring环境信息进行销毁:
- org.springframework.web.context.ContextLoaderListener
- public void contextDestroyed(ServletContextEvent event) {
- if (this.contextLoader != null) {
- this.contextLoader.closeWebApplicationContext(event.getServletContext());
- }
- ContextCleanupListener.cleanupAttributes(event.getServletContext());
- }
具体清理销毁了spring的什么东西,自己再跟踪下代码即可。
spring的启动过程:
-
首先,对于一个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.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
-
再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是mlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。
说完了spring上下文的初始化过程,这三个上下文的关系应该就了解了。如还是不太清楚,我就爱莫能助了,只能自行看代码去了。