zoukankan      html  css  js  c++  java
  • [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对象了. 

     

  • 相关阅读:
    Maven创建项目: Failed to execute goal org.apache.maven.plugin( mvn archetype:create)
    maven仓库--私服(Nexus的配置使用)
    maven仓库--私服(Nexus的配置使用)
    maven仓库--私服(Nexus的配置使用)
    maven中snapshot快照库和release发布库的区别和作用
    maven中snapshot快照库和release发布库的区别和作用
    maven中snapshot快照库和release发布库的区别和作用
    Bugzilla使用手册及解决方案
    jQuery常见的几个文档处理方式
    正则表达式实现对多个表格的某一指定字段的样式添加
  • 原文地址:https://www.cnblogs.com/wang-meng/p/5598731.html
Copyright © 2011-2022 走看看