[Spring框架] Spring中的 ContextLoaderListener 实现原理.

前言: 这是关于Spring的第三篇文章, 打算后续还会写入AOP 和Spring 事务管理相关的文章, 这么好的两个周末 都在看code了, 确实是有所收获, 现在就来记录一下.

在上一篇讲解Spring IOC的文章中, 每次产生ApplicationContext工厂的方式是: 
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

这样产生applicationContext 就有一个弊端, 每次访问加载bean 的时候都会产生这个工厂, 所以 这里需要解决这个问题.
解决问题的方法很简单, 在web 启动的时候将applicationContext转到到servletContext中, 因为在web 应用中的所有servlet都共享一个servletContext对象. 那么我们就可以利用ServletContextListener去监听servletContext事件, 当web 应用启动的是时候, 我们就将applicationContext 装载到servletContext中.

然而Spring容器底层已经为我们想到了这一点, 在spring-web-xxx-release.jar包中有一个 已经实现了ServletContextListener的类, 下面我们就来看一下这个类: 

[Spring框架] Spring中的 ContextLoaderListener 实现原理._Java

  1 public class ContextLoaderListener extends ContextLoader implements ServletContextListener {  2   3     private ContextLoader contextLoader;  4   5   6     /**  7      * Create a new {@code ContextLoaderListener} that will create a web application  8      * context based on the "contextClass" and "contextConfigLocation" servlet  9      * context-params. See {@link ContextLoader} superclass documentation for details on 10      * default values for each. 11      * <p>This constructor is typically used when declaring {@code ContextLoaderListener} 12      * as a {@code <listener>} within {@code web.xml}, where a no-arg constructor is 13      * required. 14      * <p>The created application context will be registered into the ServletContext under 15      * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} 16      * and the Spring application context will be closed when the {@link #contextDestroyed} 17      * lifecycle method is invoked on this listener. 18      * @see ContextLoader 19      * @see #ContextLoaderListener(WebApplicationContext) 20      * @see #contextInitialized(ServletContextEvent) 21      * @see #contextDestroyed(ServletContextEvent) 22      */ 23     public ContextLoaderListener() { 24     } 25  26     /** 27      * Create a new {@code ContextLoaderListener} with the given application context. This 28      * constructor is useful in Servlet 3.0+ environments where instance-based 29      * registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener} 30      * API. 31      * <p>The context may or may not yet be {@linkplain 32      * org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it 33      * (a) is an implementation of {@link ConfigurableWebApplicationContext} and 34      * (b) has <strong>not</strong> already been refreshed (the recommended approach), 35      * then the following will occur: 36      * <ul> 37      * <li>If the given context has not already been assigned an {@linkplain 38      * org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it</li> 39      * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to 40      * the application context</li> 41      * <li>{@link #customizeContext} will be called</li> 42      * <li>Any {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}s 43      * specified through the "contextInitializerClasses" init-param will be applied.</li> 44      * <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called</li> 45      * </ul> 46      * If the context has already been refreshed or does not implement 47      * {@code ConfigurableWebApplicationContext}, none of the above will occur under the 48      * assumption that the user has performed these actions (or not) per his or her 49      * specific needs. 50      * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples. 51      * <p>In any case, the given application context will be registered into the 52      * ServletContext under the attribute name {@link 53      * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring 54      * application context will be closed when the {@link #contextDestroyed} lifecycle 55      * method is invoked on this listener. 56      * @param context the application context to manage 57      * @see #contextInitialized(ServletContextEvent) 58      * @see #contextDestroyed(ServletContextEvent) 59      */ 60     public ContextLoaderListener(WebApplicationContext context) { 61         super(context); 62     } 63  64     /** 65      * Initialize the root web application context. 66      */ 67     public void contextInitialized(ServletContextEvent event) { 68         this.contextLoader = createContextLoader(); 69         if (this.contextLoader == null) { 70             this.contextLoader = this; 71         } 72         this.contextLoader.initWebApplicationContext(event.getServletContext()); 73     } 74  75     /** 76      * Create the ContextLoader to use. Can be overridden in subclasses. 77      * @return the new ContextLoader 78      * @deprecated in favor of simply subclassing ContextLoaderListener itself 79      * (which extends ContextLoader, as of Spring 3.0) 80      */ 81     @Deprecated 82     protected ContextLoader createContextLoader() { 83         return null; 84     } 85  86     /** 87      * Return the ContextLoader used by this listener. 88      * @return the current ContextLoader 89      * @deprecated in favor of simply subclassing ContextLoaderListener itself 90      * (which extends ContextLoader, as of Spring 3.0) 91      */ 92     @Deprecated 93     public ContextLoader getContextLoader() { 94         return this.contextLoader; 95     } 96  97  98     /** 99      * Close the root web application context.100      */101     public void contextDestroyed(ServletContextEvent event) {102         if (this.contextLoader != null) {103             this.contextLoader.closeWebApplicationContext(event.getServletContext());104         }105         ContextCleanupListener.cleanupAttributes(event.getServletContext());106     }107 108 }

[Spring框架] Spring中的 ContextLoaderListener 实现原理._Java

 

这里就监听到了servletContext的创建过程, 那么 这个类又是如何将applicationContext装入到serveletContext容器中的呢? 
我们接着来看看 :this.contextLoader.initWebApplicationContext(event.getServletContext()) 方法:

[Spring框架] Spring中的 ContextLoaderListener 实现原理._Java

 1 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { 2         if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { 3             throw new IllegalStateException( 4                     "Cannot initialize context because there is already a root application context present - " + 5                     "check whether you have multiple ContextLoader* definitions in your web.xml!"); 6         } 7  8         Log logger = LogFactory.getLog(ContextLoader.class); 9         servletContext.log("Initializing Spring root WebApplicationContext");10         if (logger.isInfoEnabled()) {11             logger.info("Root WebApplicationContext: initialization started");12         }13         long startTime = System.currentTimeMillis();14 15         try {16             // Store context in local instance variable, to guarantee that17             // it is available on ServletContext shutdown.18             if (this.context == null) {19                 this.context = createWebApplicationContext(servletContext);20             }21             if (this.context instanceof ConfigurableWebApplicationContext) {22                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;23                 if (!cwac.isActive()) {24                     // The context has not yet been refreshed -> provide services such as25                     // setting the parent context, setting the application context id, etc26                     if (cwac.getParent() == null) {27                         // The context instance was injected without an explicit parent ->28                         // determine parent for root web application context, if any.29                         ApplicationContext parent = loadParentContext(servletContext);30                         cwac.setParent(parent);31                     }32                     configureAndRefreshWebApplicationContext(cwac, servletContext);33                 }34             }35             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);36 37             ClassLoader ccl = Thread.currentThread().getContextClassLoader();38             if (ccl == ContextLoader.class.getClassLoader()) {39                 currentContext = this.context;40             }41             else if (ccl != null) {42                 currentContextPerThread.put(ccl, this.context);43             }44 45             if (logger.isDebugEnabled()) {46                 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +47                         WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");48             }49             if (logger.isInfoEnabled()) {50                 long elapsedTime = System.currentTimeMillis() - startTime;51                 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");52             }53 54             return this.context;55         }56         catch (RuntimeException ex) {57             logger.error("Context initialization failed", ex);58             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);59             throw ex;60         }61         catch (Error err) {62             logger.error("Context initialization failed", err);63             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);64             throw err;65         }66     }

[Spring框架] Spring中的 ContextLoaderListener 实现原理._Java

这里的重点是:servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
用key:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE   value: this.context的形式将applicationContext装载到servletContext中了. 
另外从上面的一些注释我们可以看出:  WEB-INF/applicationContext.xml, 如果我们项目中的配置文件不是这么一个路径的话  那么我们使用ContextLoaderListener 就会 出问题, 所以我们还需要在web.xml中配置我们的applicationContext.xml配置文件的路径.

[Spring框架] Spring中的 ContextLoaderListener 实现原理._Java

1 <listener>2     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>3 </listener>4 5 <context-param>6     <param-name>contextConfigLocation</param-name>7     <param-value>classpath:applicationContext.xml</param-value>8 </context-param>

[Spring框架] Spring中的 ContextLoaderListener 实现原理._Java


剩下的就是在项目中开始使用 servletContext中装载的applicationContext对象了:
那么这里又有一个问题, 装载时的key 是 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 我们在代码中真的要使用这个吗? 其实Spring为我们提供了一个工具类WebApplicationContextUtils, 接着我们先看下如何使用, 然后再去看下这个工具类的源码: 

WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());

接着来看下这个工具了的源码: 

[Spring框架] Spring中的 ContextLoaderListener 实现原理._Java

 1 /** 2      * Find the root WebApplicationContext for this web application, which is 3      * typically loaded via {@link org.springframework.web.context.ContextLoaderListener}. 4      * <p>Will rethrow an exception that happened on root context startup, 5      * to differentiate between a failed context startup and no context at all. 6      * @param sc ServletContext to find the web application context for 7      * @return the root WebApplicationContext for this web app, or {@code null} if none 8      * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 9      */10     public static WebApplicationContext getWebApplicationContext(ServletContext sc) {11         return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);12     }

[Spring框架] Spring中的 ContextLoaderListener 实现原理._Java

[Spring框架] Spring中的 ContextLoaderListener 实现原理._Java

 1 /** 2      * Find a custom WebApplicationContext for this web application. 3      * @param sc ServletContext to find the web application context for 4      * @param attrName the name of the ServletContext attribute to look for 5      * @return the desired WebApplicationContext for this web app, or {@code null} if none 6      */ 7     public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) { 8         Assert.notNull(sc, "ServletContext must not be null"); 9         Object attr = sc.getAttribute(attrName);10         if (attr == null) {11             return null;12         }13         if (attr instanceof RuntimeException) {14             throw (RuntimeException) attr;15         }16         if (attr instanceof Error) {17             throw (Error) attr;18         }19         if (attr instanceof Exception) {20             throw new IllegalStateException((Exception) attr);21         }22         if (!(attr instanceof WebApplicationContext)) {23             throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);24         }25         return (WebApplicationContext) attr;26     }

[Spring框架] Spring中的 ContextLoaderListener 实现原理._Java

这里就能很直观清晰地看到  通过key值直接获取到装载到servletContext中的 applicationContext对象了. 

 

 

分类: Spring源码阅读

好文要顶 关注我 收藏该文 [Spring框架] Spring中的 ContextLoaderListener 实现原理._Java_11 [Spring框架] Spring中的 ContextLoaderListener 实现原理._Java_12

[Spring框架] Spring中的 ContextLoaderListener 实现原理._Java_13

一枝花算不算浪漫