[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的类, 下面我们就来看一下这个类: 

  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 }

 

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

 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 that
17             // 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 as
25                     // setting the parent context, setting the application context id, etc
26                     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     }

这里的重点是: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配置文件的路径.

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>


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

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

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

 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     }
 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     }

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

 

 

posted @ 2016-06-19 20:21  一枝花算不算浪漫  阅读(12811)  评论(0编辑  收藏  举报