[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的类, 下面我们就来看一下这个类:
 public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
     private ContextLoader contextLoader;
     /**
      * Create a new {@code ContextLoaderListener} that will create a web application
      * context based on the "contextClass" and "contextConfigLocation" servlet
      * context-params. See {@link ContextLoader} superclass documentation for details on
      * default values for each.
      * <p>This constructor is typically used when declaring {@code ContextLoaderListener}
      * as a {@code <listener>} within {@code web.xml}, where a no-arg constructor is
      * required.
      * <p>The created application context will be registered into the ServletContext under
      * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
      * and the Spring application context will be closed when the {@link #contextDestroyed}
      * lifecycle method is invoked on this listener.
      * @see ContextLoader
      * @see #ContextLoaderListener(WebApplicationContext)
      * @see #contextInitialized(ServletContextEvent)
      * @see #contextDestroyed(ServletContextEvent)
      */
     public ContextLoaderListener() {
     }
     /**
      * Create a new {@code ContextLoaderListener} with the given application context. This
      * constructor is useful in Servlet 3.0+ environments where instance-based
      * registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener}
      * API.
      * <p>The context may or may not yet be {@linkplain
      * org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it
      * (a) is an implementation of {@link ConfigurableWebApplicationContext} and
      * (b) has <strong>not</strong> already been refreshed (the recommended approach),
      * then the following will occur:
      * <ul>
      * <li>If the given context has not already been assigned an {@linkplain
      * org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
      * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
      * the application context</li>
      * <li>{@link #customizeContext} will be called</li>
      * <li>Any {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}s
      * specified through the "contextInitializerClasses" init-param will be applied.</li>
      * <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called</li>
      * </ul>
      * If the context has already been refreshed or does not implement
      * {@code ConfigurableWebApplicationContext}, none of the above will occur under the
      * assumption that the user has performed these actions (or not) per his or her
      * specific needs.
      * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
      * <p>In any case, the given application context will be registered into the
      * ServletContext under the attribute name {@link
      * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring
      * application context will be closed when the {@link #contextDestroyed} lifecycle
      * method is invoked on this listener.
      * @param context the application context to manage
      * @see #contextInitialized(ServletContextEvent)
      * @see #contextDestroyed(ServletContextEvent)
      */
     public ContextLoaderListener(WebApplicationContext context) {
         super(context);
     }
     /**
      * Initialize the root web application context.
      */
     public void contextInitialized(ServletContextEvent event) {
         this.contextLoader = createContextLoader();
         if (this.contextLoader == null) {
             this.contextLoader = this;
         }
         this.contextLoader.initWebApplicationContext(event.getServletContext());
     }
     /**
      * Create the ContextLoader to use. Can be overridden in subclasses.
      * @return the new ContextLoader
      * @deprecated in favor of simply subclassing ContextLoaderListener itself
      * (which extends ContextLoader, as of Spring 3.0)
      */
     @Deprecated
     protected ContextLoader createContextLoader() {
         return null;
     }
     /**
      * Return the ContextLoader used by this listener.
      * @return the current ContextLoader
      * @deprecated in favor of simply subclassing ContextLoaderListener itself
      * (which extends ContextLoader, as of Spring 3.0)
      */
     @Deprecated
     public ContextLoader getContextLoader() {
         return this.contextLoader;
     }
     /**
      * Close the root web application context.
      */
     public void contextDestroyed(ServletContextEvent event) {
         if (this.contextLoader != null) {
             this.contextLoader.closeWebApplicationContext(event.getServletContext());
         }
         ContextCleanupListener.cleanupAttributes(event.getServletContext());
     }
 }
这里就监听到了servletContext的创建过程, 那么 这个类又是如何将applicationContext装入到serveletContext容器中的呢? 
我们接着来看看 :this.contextLoader.initWebApplicationContext(event.getServletContext()) 方法:
 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
         if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
             throw new IllegalStateException(
                     "Cannot initialize context because there is already a root application context present - " +
                     "check whether you have multiple ContextLoader* definitions in your web.xml!");
         }
         Log logger = LogFactory.getLog(ContextLoader.class);
         servletContext.log("Initializing Spring root WebApplicationContext");
         if (logger.isInfoEnabled()) {
             logger.info("Root WebApplicationContext: initialization started");
         }
         long startTime = System.currentTimeMillis();
         try {
             // Store context in local instance variable, to guarantee that
             // it is available on ServletContext shutdown.
             if (this.context == null) {
                 this.context = createWebApplicationContext(servletContext);
             }
             if (this.context instanceof ConfigurableWebApplicationContext) {
                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                 if (!cwac.isActive()) {
                     // The context has not yet been refreshed -> provide services such as
                     // setting the parent context, setting the application context id, etc
                     if (cwac.getParent() == null) {
                         // The context instance was injected without an explicit parent ->
                         // determine parent for root web application context, if any.
                         ApplicationContext parent = loadParentContext(servletContext);
                         cwac.setParent(parent);
                     }
                     configureAndRefreshWebApplicationContext(cwac, servletContext);
                 }
             }
             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
             ClassLoader ccl = Thread.currentThread().getContextClassLoader();
             if (ccl == ContextLoader.class.getClassLoader()) {
                 currentContext = this.context;
             }
             else if (ccl != null) {
                 currentContextPerThread.put(ccl, this.context);
             }
             if (logger.isDebugEnabled()) {
                 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                         WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
             }
             if (logger.isInfoEnabled()) {
                 long elapsedTime = System.currentTimeMillis() - startTime;
                 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
             }
             return this.context;
         }
         catch (RuntimeException ex) {
             logger.error("Context initialization failed", ex);
             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
             throw ex;
         }
         catch (Error err) {
             logger.error("Context initialization failed", err);
             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
             throw err;
         }
     }
这里的重点是: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配置文件的路径.
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> <context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
剩下的就是在项目中开始使用 servletContext中装载的applicationContext对象了:
那么这里又有一个问题, 装载时的key 是 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 我们在代码中真的要使用这个吗? 其实Spring为我们提供了一个工具类WebApplicationContextUtils, 接着我们先看下如何使用, 然后再去看下这个工具类的源码:
WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
接着来看下这个工具了的源码:
/**
* Find the root WebApplicationContext for this web application, which is
* typically loaded via {@link org.springframework.web.context.ContextLoaderListener}.
* <p>Will rethrow an exception that happened on root context startup,
* to differentiate between a failed context startup and no context at all.
* @param sc ServletContext to find the web application context for
* @return the root WebApplicationContext for this web app, or {@code null} if none
* @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
*/
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
/**
* Find a custom WebApplicationContext for this web application.
* @param sc ServletContext to find the web application context for
* @param attrName the name of the ServletContext attribute to look for
* @return the desired WebApplicationContext for this web app, or {@code null} if none
*/
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
Assert.notNull(sc, "ServletContext must not be null");
Object attr = sc.getAttribute(attrName);
if (attr == null) {
return null;
}
if (attr instanceof RuntimeException) {
throw (RuntimeException) attr;
}
if (attr instanceof Error) {
throw (Error) attr;
}
if (attr instanceof Exception) {
throw new IllegalStateException((Exception) attr);
}
if (!(attr instanceof WebApplicationContext)) {
throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
}
return (WebApplicationContext) attr;
}
这里就能很直观清晰地看到 通过key值直接获取到装载到servletContext中的 applicationContext对象了.
[Spring框架] Spring中的 ContextLoaderListener 实现原理.的更多相关文章
- Spring框架 - Spring和Spring框架组成
		Spring框架 - Spring和Spring框架组成 Spring是什么?它是怎么诞生的?有哪些主要的组件和核心功能呢? 本文通过这几个问题帮助你构筑Spring和Spring Framework ... 
- [Spring框架]Spring AOP基础入门总结二:Spring基于AspectJ的AOP的开发.
		前言: 在上一篇中: [Spring框架]Spring AOP基础入门总结一. 中 我们已经知道了一个Spring AOP程序是如何开发的, 在这里呢我们将基于AspectJ来进行AOP 的总结和学习 ... 
- 跟着刚哥学习Spring框架--Spring容器(二)
		Spring容器 启动Spring容器(实例化容器) -- IOC容器读取Bean配置创建Bean实例之前,必须对它进行实例化(加载启动),这样才可以从容器中获取Bean的实例并使用. Bean是S ... 
- [Spring框架]Spring IOC的原理及详解。
		这里感谢 CSDN 的原博客:http://blog.csdn.net/m13666368773/article/details/7802126 看后 受益匪浅,这里再重温一遍Spring IOC ... 
- JavaWeb_(Spring框架)Spring中IoC与DI概念入门
		Spring是于2003 年兴起的一个轻量级的Java 开源框架,它由Rod Johnson创建.传统J2EE应用的开发效率低,Spring作为开源的中间件,提供J2EE应用的各层的解决方案,Spri ... 
- Spring框架学习06——AOP底层实现原理
		在Java中有多种动态代理技术,如JDK.CGLIB.Javassist.ASM,其中最常用的动态代理技术是JDK和CGLIB. 1.JDK的动态代理 JDK动态代理是java.lang.reflec ... 
- JavaWeb_(Spring框架)Spring整合Hibernate
		Dao层类要继承HibernateDaoSupport.java父类 原先使用Hibernate框架hibernate.cfg.xml配置数据库 <hibernate-configuration ... 
- [Spring框架]Spring AOP基础入门总结一.
		前言:前面已经有两篇文章讲了Spring IOC/DI 以及 使用xml和注解两种方法开发的案例, 下面就来梳理一下Spring的另一核心AOP. 一, 什么是AOP 在软件业,AOP为Aspect ... 
- [Spring框架]Spring开发实例: XML+注解.
		前言: 本文为自己学习Spring记录所用, 文章内容包括Spring的概述已经简单开发, 主要涉及IOC相关知识, 希望能够对新入门Spring的同学有帮助, 也希望大家一起讨论相关的知识. 一. ... 
- Spring框架---Spring入门
		Spring入门 为了能更好的理解先讲一些有的没的的东西: 什么是Spring Spring是分层的JavaSE/EE full-stack(一站式) 轻量级开源框架 分层 SUN提供的EE的三层结构 ... 
随机推荐
- navicat自动备份数据
			1.打开navicat客户端,连上mysql后,双击左边你想要备份的数据库.点击"计划",再点击"新建批处理作业". 2.双击上面的可用任务,它就会到下面的列表 ... 
- Everything搜索结果显示0 Object
			比较过windows本身的文档搜索功能,Everything的本地文档搜索能力简直令人咋舌,更逆天的是软件本身体积很小. 问题:打开everything时,文件列表消失,软件下方信息为0 object ... 
- ubuntu gcc-5 安装
			安装了一个ubuntu 15.10,没有集成vim,很失望,先安装个vim,sudo apt-get install vim. 开始获取g++-5: $ sudo add-apt-repository ... 
- VS2012中,C# 配置文件读取 + C#多个工程共享共有变量 + 整理using语句
			(一) C# 配置文件读取 C#工程可以自动生成配置文件,以便整个工程可以使用设置的配置进行后续的处理工作. 1. 首先,右键工程文件-->Properties -->settings-- ... 
- shell学习
			set -x 进入调试模式,会把每一个命令实际执行的命令打印出来,也就是会把一些参数扩展后的样子打印出来. set +x 退出调试模式自定义变量:x=7,y=8echo `expr $x + $y` ... 
- disconf使用
			1.创建app,确定version 2.创建配置文件redis.config 3.选择app下env环境,上传redis.config到disconf 4.创建disconf.properties到c ... 
- [转] spring @Entity @Table
			实体bean,entity 注解设置 持久化是位于JDBC之上的一个更高层抽象.持久层将对象映射到数据库,以便在查询.装载.更新或删除对象的时候,无须使用像JDBC那样繁琐的API.EJB的早期版本中 ... 
- Yaf零基础学习总结5-Yaf类的自动加载
			Yaf零基础学习总结5-Yaf类的自动加载 框架的一个重要功能就是类的自动加载了,在第一个demo的时候我们就约定自己的项目的目录结构,框架就基于这个目录结构来自动加载需要的类文件. Yaf在自启动的 ... 
- Runtime消息传送
			person.h #import<Foundation/Foundation.h> @interfacePerson :NSObject + (void)eat; - (void)run: ... 
- 【leetcode】Trapping Rain Water
			Given n non-negative integers representing an elevation map where the width of each bar is 1, comput ... 
