[Spring框架] Spring中的 ContextLoaderListener 实现原理.
2016-06-19 20:21
483 查看
前言: 这是关于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的类, 下面我们就来看一下这个类:
这里就监听到了servletContext的创建过程, 那么 这个类又是如何将applicationContext装入到serveletContext容器中的呢?
我们接着来看看 :this.contextLoader.initWebApplicationContext(event.getServletContext()) 方法:
这里的重点是: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配置文件的路径.
剩下的就是在项目中开始使用 servletContext中装载的applicationContext对象了:
那么这里又有一个问题, 装载时的key 是 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 我们在代码中真的要使用这个吗? 其实Spring为我们提供了一个工具类WebApplicationContextUtils, 接着我们先看下如何使用, 然后再去看下这个工具类的源码:
接着来看下这个工具了的源码:
这里就能很直观清晰地看到 通过key值直接获取到装载到servletContext中的 applicationContext对象了.
在上一篇讲解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 mvc 拦截器 配置心得
- 【SpringMVC学习03】SpringMVC中注解和非注解方式下的映射器和适配器总结
- java 个人总结
- 项目相关错误系列之项目上面的小红叉
- java保存两位小数
- SpringMVC与Struts2的对比
- 使用Spring Boot创建一个应用
- 20145240《Java程序设计》课程总结
- SpringMVC中的日期转换器和编码过滤器
- SpringMVC拦截器(资源和权限管理)
- springMVC 拦截器简单配置
- Java web 开发简介
- Java NIO和IO的区别
- Spring mvc Interceptor 解决Session超时配置流程
- Java 实现日期 Date 的赋值
- java之jce
- SpringMVC加载自定义目录下的springmvc.xml配置文件
- Java泛型简介
- 学习javaweb过程中遇到的一些小error
- eclipse插件