SpringMVC容器初始化篇----ContextLoaderListener

学习学习容器初始化,若有不对的地方,请指出更正,大家共同学习学习。

此篇幅主要围绕着 ContextLoaderListener加载容器,理解其中的原理。

ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。

因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。

ContextLoaderListener启动的上下文为根上下文,DispatcherServlet所创建的上下文的的父上下文即为此根上下文,可在FrameworkServlet中的initWebApplicationContext中看出。


通常在web.xml中如下配置:

[html]  view plain  copy
  1. <context-param>  
  2.         <param-name>contextConfigLocation</param-name>  
  3.         <param-value>classpath*:server_spring.xml  
  4.         </param-value>  
  5.     </context-param>  
  6.     <listener>  
  7.         <listener-class>org.springframework.web.context.ContextLoaderListener  
  8.         </listener-class>  
  9.     </listener>  


层次结构

                 
    

ContextLoaderListener继承ContextLoader类实现ServletContextListener接口
其中它的主要功能是在ContextLoader中实现,ServletContextListener接口在package javax.servlet中以为servlet的api,
ServletContextListener又继承EventListener,此乃package java.util;中的接口了。
EventListener接口中无任何方法。
ServletContextListener中含有2方法,一个初始化一个销毁。
[java]  view plain  copy
  1. /** 
  2.      * Receives notification that the web application initialization 
  3.      * process is starting. 
  4.      * 
  5.      * <p>All ServletContextListeners are notified of context 
  6.      * initialization before any filters or servlets in the web 
  7.      * application are initialized. 
  8.      * 
  9.      * @param sce the ServletContextEvent containing the ServletContext 
  10.      * that is being initialized 
  11.      */  
  12.     public void contextInitialized(ServletContextEvent sce);  
  13.   
  14.     /** 
  15.      * Receives notification that the ServletContext is about to be 
  16.      * shut down. 
  17.      * 
  18.      * <p>All servlets and filters will have been destroyed before any 
  19.      * ServletContextListeners are notified of context 
  20.      * destruction. 
  21.      * 
  22.      * @param sce the ServletContextEvent containing the ServletContext 
  23.      * that is being destroyed 
  24.      */  
  25.     public void contextDestroyed(ServletContextEvent sce);  


ContextLoaderListener初始化容器时序图


在ContextLoaderListener中的contextInitialized方法
[java]  view plain  copy
  1. /** 
  2.      * Initialize the root web application context. 
  3.      */  
  4.     @Override  
  5.     public void contextInitialized(ServletContextEvent event) {  
  6.         initWebApplicationContext(event.getServletContext());  
  7.     }  

初始化root跟web上下文,initWebApplicationContext方法在其父类ContextLoader中提供实现。

ContextLoader中initWebApplicationContext方法初始化根上下文
[java]  view plain  copy
  1. /** 
  2.      * Initialize Spring's web application context for the given servlet context, 
  3.      * using the application context provided at construction time, or creating a new one 
  4.      * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and 
  5.      * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params. 
  6.      * @param servletContext current servlet context 
  7.      * @return the new WebApplicationContext 
  8.      * @see #ContextLoader(WebApplicationContext) 
  9.      * @see #CONTEXT_CLASS_PARAM 
  10.      * @see #CONFIG_LOCATION_PARAM 
  11.      */  
  12.     public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {  
  13.         //这里判断是否在ServletContext中存在上下文,如果有,说明已载入过或配置文件出错,可以从错误信息中看出  
  14.         if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != ) {  
  15.             throw new IllegalStateException(  
  16.                     "Cannot initialize context because there is already a root application context present - " +  
  17.                     "check whether you have multiple ContextLoader* definitions in your web.xml!");  
  18.         }  
  19.   
  20.         Log logger = LogFactory.getLog(ContextLoader.class);  
  21.         servletContext.log("Initializing Spring root WebApplicationContext");  
  22.         if (logger.isInfoEnabled()) {  
  23.             logger.info("Root WebApplicationContext: initialization started");  
  24.         }  
  25.         long startTime = System.currentTimeMillis();  
  26.   
  27.         try {  
  28.             // Store context in local instance variable, to guarantee that  
  29.             // it is available on ServletContext shutdown.  
  30.             if (this.context == ) {  
  31.                 this.context = createWebApplicationContext(servletContext);  
  32.             }  
  33.             if (this.context instanceof ConfigurableWebApplicationContext) {  
  34.                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;  
  35.                 if (!cwac.isActive()) {  
  36.                     // The context has not yet been refreshed -> provide services such as  
  37.                     // setting the parent context, setting the application context id, etc  
  38.                     if (cwac.getParent() == ) {  
  39.                         // The context instance was injected without an explicit parent ->  
  40.                         // determine parent for root web application context, if any.  
  41.                         // 这里载入根上下文的父上下文     
  42.                         ApplicationContext parent = loadParentContext(servletContext);  
  43.                         cwac.setParent(parent);  
  44.                     }  
  45.                      //这里从web.xml中取得相关的初始化参数,对WebApplicationContext进行初始化  
  46.                     configureAndRefreshWebApplicationContext(cwac, servletContext);  
  47.                 }  
  48.             }  
  49.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);  
  50.   
  51.             ClassLoader ccl = Thread.currentThread().getContextClassLoader();  
  52.             if (ccl == ContextLoader.class.getClassLoader()) {  
  53.                 currentContext = this.context;  
  54.             }  
  55.             else if (ccl != ) {  
  56.                 currentContextPerThread.put(ccl, this.context);  
  57.             }  
  58.   
  59.             if (logger.isDebugEnabled()) {  
  60.                 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +  
  61.                         WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");  
  62.             }  
  63.             if (logger.isInfoEnabled()) {  
  64.                 long elapsedTime = System.currentTimeMillis() - startTime;  
  65.                 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");  
  66.             }  
  67.   
  68.             return this.context;  
  69.         }  
  70.         catch (RuntimeException ex) {  
  71.             logger.error("Context initialization failed", ex);  
  72.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);  
  73.             throw ex;  
  74.         }  
  75.         catch (Error err) {  
  76.             logger.error("Context initialization failed", err);  
  77.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);  
  78.             throw err;  
  79.         }  
  80.     }  

根据提供的servlet上下文去初始化Spring的web应用上下文,在构造时使用当前应用上下文或者在web.xml中配置参数contextClass和contextConfigLocation去创建新的上下文。

先判断是否在ServletContext中存在root上下文,如果有,说明已载入过或配置文件出错,可以从错误信息中看出。
通过createWebApplicationContext方法创建web应用上下文,此上下文必定是实现了ConfigurableWebApplicationContext接口,在设置parent for root web application context,在configureAndRefreshWebApplicationContext方法里构造bean工厂和容器里bean的创建,这里就不描述了,下次专门研究这块,最后将跟上下文存入servletContext里,同时根web应用上下文存入到currentContextPerThread,可供后续取出当前上下文,currentContextPerThread = new ConcurrentHashMap<ClassLoader, WebApplicationContext>(1);。

ContextLoader中createWebApplicationContext方法创建根上下文
[java]  view plain  copy
  1. /** 
  2.      * Instantiate the root WebApplicationContext for this loader, either the 
  3.      * default context class or a custom context class if specified. 
  4.      * <p>This implementation expects custom contexts to implement the 
  5.      * {@link ConfigurableWebApplicationContext} interface. 
  6.      * Can be overridden in subclasses. 
  7.      * <p>In addition, {@link #customizeContext} gets called prior to refreshing the 
  8.      * context, allowing subclasses to perform custom modifications to the context. 
  9.      * @param sc current servlet context 
  10.      * @return the root WebApplicationContext 
  11.      * @see ConfigurableWebApplicationContext 
  12.      */  
  13.     protected WebApplicationContext createWebApplicationContext(ServletContext sc) {  
  14.         //这里需要确定我们载入的根WebApplication的类型,  
  15.         //由在web.xml中配置的contextClass中配置的参数, 如果没有使用默认的。   
  16.         Class<?> contextClass = determineContextClass(sc);  
  17.         //contextClass必须实现ConfigurableWebApplicationContext,否则抛异常  
  18.         if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {  
  19.             throw new ApplicationContextException("Custom context class [" + contextClass.getName() +  
  20.                     "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");  
  21.         }  
  22.         //初始化WebApplication,强转成ConfigurableWebApplicationContext  
  23.         return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);  
  24.     }  

初始化根上下文,
最后返回值需强转成ConfigurableWebApplicationContext。

ContextLoader中determineContextClass方法找到根上下文的Class类型
[java]  view plain  copy
  1. /** 
  2.      * Return the WebApplicationContext implementation class to use, either the 
  3.      * default XmlWebApplicationContext or a custom context class if specified. 
  4.      * @param servletContext current servlet context 
  5.      * @return the WebApplicationContext implementation class to use 
  6.      * @see #CONTEXT_CLASS_PARAM 
  7.      * @see org.springframework.web.context.support.XmlWebApplicationContext 
  8.      */  
  9.     protected Class<?> determineContextClass(ServletContext servletContext) {  
  10.         String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);  
  11.         if (contextClassName != ) {  
  12.             try {  
  13.                 return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());  
  14.             }  
  15.             catch (ClassNotFoundException ex) {  
  16.                 throw new ApplicationContextException(  
  17.                         "Failed to load custom context class [" + contextClassName + "]", ex);  
  18.             }  
  19.         }  
  20.         else {  
  21.             contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());  
  22.             try {  
  23.                 return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());  
  24.             }  
  25.             catch (ClassNotFoundException ex) {  
  26.                 throw new ApplicationContextException(  
  27.                         "Failed to load default context class [" + contextClassName + "]", ex);  
  28.             }  
  29.         }  
  30.     }  
Web.xml中配置了contextClass就取其值,但必须是实现ConfigurableWebApplicationContext,
没有的就取默认值XmlWebApplicationContext。

ContextClass默认值和ContextLoader.properties如下:

[java]  view plain  copy
  1. /** 
  2.      * Name of the class path resource (relative to the ContextLoader class) 
  3.      * that defines ContextLoader's default strategy names. 
  4.      */  
  5.     private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";  
  6.   
  7.   
  8.     private static final Properties defaultStrategies;  
  9.   
  10.     static {  
  11.         // Load default strategy implementations from properties file.  
  12.         // This is currently strictly internal and not meant to be customized  
  13.         // by application developers.  
  14.         try {  
  15.             ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);  
  16.             defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);  
  17.         }  
  18.         catch (IOException ex) {  
  19.             throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());  
  20.         }  
  21.     }  

[plain]  view plain  copy
  1. # Default WebApplicationContext implementation class for ContextLoader.  
  2. # Used as fallback when no explicit context implementation has been specified as context-param.  
  3. # Not meant to be customized by application developers.  
  4.   
  5. org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext  


其中loadParentContext
[java]  view plain  copy
  1. /** 
  2.      * Template method with default implementation (which may be overridden by a 
  3.      * subclass), to load or obtain an ApplicationContext instance which will be 
  4.      * used as the parent context of the root WebApplicationContext. If the 
  5.      * return value from the method is null, no parent context is set. 
  6.      * <p>The main reason to load a parent context here is to allow multiple root 
  7.      * web application contexts to all be children of a shared EAR context, or 
  8.      * alternately to also share the same parent context that is visible to 
  9.      * EJBs. For pure web applications, there is usually no need to worry about 
  10.      * having a parent context to the root web application context. 
  11.      * <p>The default implementation uses 
  12.      * {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator}, 
  13.      * configured via {@link #LOCATOR_FACTORY_SELECTOR_PARAM} and 
  14.      * {@link #LOCATOR_FACTORY_KEY_PARAM}, to load a parent context 
  15.      * which will be shared by all other users of ContextsingletonBeanFactoryLocator 
  16.      * which also use the same configuration parameters. 
  17.      * @param servletContext current servlet context 
  18.      * @return the parent application context, or {@code null} if none 
  19.      * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator 
  20.      */  
  21.     protected ApplicationContext loadParentContext(ServletContext servletContext) {  
  22.         ApplicationContext parentContext = ;  
  23.         String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);  
  24.         String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);  
  25.   
  26.         if (parentContextKey != ) {  
  27.             // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"  
  28.             BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);  
  29.             Log logger = LogFactory.getLog(ContextLoader.class);  
  30.             if (logger.isDebugEnabled()) {  
  31.                 logger.debug("Getting parent context definition: using parent context key of '" +  
  32.                         parentContextKey + "' with BeanFactoryLocator");  
  33.             }  
  34.             this.parentContextRef = locator.useBeanFactory(parentContextKey);  
  35.             parentContext = (ApplicationContext) this.parentContextRef.getFactory();  
  36.         }  
  37.   
  38.         return parentContext;  
  39.     }  

根据在web.xml中配置的locatorFactorySelector和parentContextKey来给根web应用上下设置父上下文,如果没配置的话,父上下文为空。
加载父上下文的主要原因是允许多重root web application contexts作为可共享的ERA context的子节点,或者对EJB可见的去交替共享同样的父上下文。For pure web applications, there is usually no need to worry about having a parent context to the root web application context。这句话明确告诉我们,对于纯粹的Web应用,通常不用担心root web application context的父上下文,也就是没有,为null。

在应用程序如何获取 WebApplicationContext 有多种方式,最简单的就是

1.WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();

当前应用的WebApplicationContext就保存在 ContextLoader的currentContextPerThread属性当中


2.基于ServletContext上下文获取的方式

ServletContext sc = request.getSession().getServletContext();

ApplicationContext ac1 = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);

ApplicationContext ac2 = WebApplicationContextUtils.getWebApplicationContext(sc);

WebApplicationContext wac1 = (WebApplicationContext) sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);


3.还有一些更合适的,基于Spring提供的抽象类或者接口,在初始化Bean时注入ApplicationContext

3.1:继承自抽象类ApplicationObjectSupport

说明:抽象类ApplicationObjectSupport提供getApplicationContext()方法,可以方便的获取到ApplicationContext。

Spring初始化时,会通过该抽象类的setApplicationContext(ApplicationContext context)方法将ApplicationContext 对象注入。


3.2:继承自抽象类WebApplicationObjectSupport

说明:类似上面方法,调用getWebApplicationContext()获取WebApplicationContext


3.3:实现接口ApplicationContextAware

说明:实现该接口的setApplicationContext(ApplicationContext context)方法,并保存ApplicationContext 对象。



总结:Context结构复杂,parentContext结构的作用,及如何的去加载bean工厂的逻辑原理。

猜你喜欢

转载自blog.csdn.net/ole_triangle_java/article/details/79649808