基本概念:
servletContext:
1、 使用ContextLoaderListener监听器的意义:
a) 为了在servlet容器启动时,就加载applicationContext.xml(spring核心配置文件)配置文件,实例化其中配置的bean类。
b)避免在使用spring容器获取bean时,重复加载配置文件。
2、 spring版本:4.2.4
3、 配置方式:
a)在web.xml文件中编写监听器,并配置全局的上下文变量,提供spring核心配置文件,代码如下:
<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> |
b) 如果配置了监听器,那么获取bean的方式就要相应的改变一下,代码如下:
1、ServletContext servletContext = ServletActionContext.getServletContext(); 2、WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext); 3、XXXService cs = (XXXService) context.getBean("XXXService"); 4、cs.save(); |
c)我们先不管为什么WebApplicationContext对象与ApplicationContext对象一样能获取bean对象,我们此处探究的是,为什么配置了监听器,我们就能从servletcontext中取得WebApplicationContext对象。
4、 源码分析:
a)首先进入org.springframework.web.context.ContextLoaderListener 源码:代码如下:
//: org.springframework.web.context.ContextLoaderListener.java
package org.springframework.web.context;
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener;
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() { }
public ContextLoaderListener(WebApplicationContext context) { super(context); }
@Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
@Override public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } } |
b)我们可以看到此处继承了 ServletContextListener 接口,实现两个方法(contextInitialized、contextDestroyed),在servlet容器启动和销毁的时候做一些事情。
c) 在ContextLoaderListener监听器这个场景下,contextInitialized方法中会调用ContextLoader 类中的initWebApplicationContext方法,实例化出一个WebApplicationContext对象,此方法接收一个servletContext对象,此对象由Servlet容器传入。
5、 然后我们转到initWebApplicationContext方法中:代码如下:
//: org.springframework.web.context.ContextLoader.java 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; } } |
a) this.context = createWebApplicationContext(servletContext);首先我们看这句代码,同样我们先不谈就它是怎么创建的,我们见名知意,就是根据servletContext参数,创建一个WebApplicationContext对象,并赋值给ContextLoader类中的内部变量 context。
b) servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); 紧接着将已经赋值的context变量,通过一个的标识名称放入servletContext对象中。
c) 看到这里,我想大家都知道为什么可以从ServletContex中取出WebApplicationContext对象了吧?
6、 接着我们分析在客户端获取WebApplicationContext对象的代码,再copy一份到这里:
1、ServletContext servletContext = ServletActionContext.getServletContext(); 2、WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext); 3、XXXService cs = (XXXService) context.getBean("XXXService"); 4、cs.save(); |
a) 首先,我们从struts2框架提供的类中,获取servletContext对象。
b) 之后通过WebApplicationContextUtil类中的getWebApplicationContext方法,从servletContext对象中,将WebApplicationContext对象取出:我们进入方法:
public static WebApplicationContext getWebApplicationContext(ServletContext sc) { return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); // 此处标识与存入时的一致 } |
c) 进而转入如下方法:
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; } |
d) 我们根据上面的两句代码: Object attr = sc.getAttribute(attrName); / return (WebApplicationContext) attr; 不难看出,将WebApplicationContext对象,从servletcontext对象中取出,强制转换并返回。
7、至此
a) 我们就取得了WebApplicationContext对象,并可以使用该对象来获取bean。而WebApplicationContext对象存放在servletcontext对象中,而servletcontext对象会一直存在于内存中,除非关闭servlet容器。这样就保证了只存在一份WebApplicationContext对象在内存中,同时也就保证了spring核心配置文件只会被加载一次。