前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Spring框架] Spring中的 ContextLoaderListener 实现原理.

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

作者头像
一枝花算不算浪漫
发布2018-05-18 11:24:05
5560
发布2018-05-18 11:24:05
举报

前言: 这是关于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的类, 下面我们就来看一下这个类: 

代码语言:javascript
复制
  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()) 方法:

代码语言:javascript
复制
 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配置文件的路径.

代码语言:javascript
复制
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, 接着我们先看下如何使用, 然后再去看下这个工具类的源码: 

代码语言:javascript
复制
WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());

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

代码语言:javascript
复制
 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     }
代码语言:javascript
复制
 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对象了. 

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-06-19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档