WebApplicationContext的三種初始化方式
ApplicationContext是Spring的核心,Context我們通常解釋為上下文環境,我想用“容器”來表述它更容易理解一些,ApplicationContext則是“應用的容器”了;在Web應用中,我們會用到WebApplicationContext,WebApplicationContext繼承自ApplicationContext;WebApplicationContext的初始化方式和BeanFactory.ApplicationContext有所區別,因為WebApplicationContext需要ServletContext實例,也就是說它必須擁有Web容器的前提下才能完成啟動的工作.有過Web開發經驗的讀者都知道可以在web.xml中配置自啟動的Servlet或定義Web容器監聽器(ServletContextListener),借助著兩者中的任何一個,我們就可以啟動Spring Web應用上下文的工作.
Spring分別提供了用於啟動WebApplicationContext的Servlet和Web容器監聽器:
org.springframework.web.context.ContextLoaderServlet;
org.springframework.web.context.ContextLoaderListener.
這兩個方法都是在web應用啟動的時候來初始化WebApplicationContext,我個人認為Listerner要比Servlet更好一些,因為Listerner監聽應用的啟動和結束,而Servlet得啟動要稍微延遲一些,如果在這時要做一些業務的操作,啟動的前後順序是有影響的。
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
那麽在ContextLoaderListener和ContextLoaderServlet中到底做了什麽呢?
以ContextLoaderListener為例,我們可以看到
public class ContextLoaderListener implements ServletContextListener { private ContextLoader contextLoader; /** * Initialize the root web application context. */ public void contextInitialized(ServletContextEvent event) { this.contextLoader = createContextLoader(); this.contextLoader.initWebApplicationContext(event.getServletContext()); } }
顯然,ContextLoaderListener實現了ServeletContextListenet,在ServletContext初始化的時候,會進行Spring的初始化,大家肯定會想,Spring的初始化應該與ServletContext有一定關系吧?有關系嗎?接下來讓我們看看
ContextLoader.initWebApplicationContext方法。
ContextLoader是一個工具類,用來初始化WebApplicationContext,其主要方法就是initWebApplicationContext,我們繼續研究initWebApplicationContext這個方法:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) throws IllegalStateException, BeansException { //從ServletContext中查找,是否存在以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE為Key的值 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!"); } try { // Determine parent for root web application context, if any. ApplicationContext parent = loadParentContext(servletContext); // it is available on ServletContext shutdown. this.context = createWebApplicationContext(servletContext, parent); //將ApplicationContext放入ServletContext中,其key為<WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); //將ApplicationContext放入ContextLoader的全局靜態常量Map中,其中key為:Thread.currentThread().getContextClassLoader()即當前線程類加載器 currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this.context); return this.context; }
從上面的代碼大家應該明白了Spring初始化之後,將ApplicationContext存到在了兩個地方(servletContext中和currentContextPerThread中),那麽是不是意味著我們可以通過兩種方式取得ApplicationContext?
第一種獲取方式:
註:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"; 即為 "org.springframework.web.context.WebApplicationContext.ROOT"
那麽咱們是不是可以這樣獲得ApplicationContext:
request.getSession().getServletContext().getAttribute("org.springframework.web.context.WebApplicationContext.ROOT")
確實可以,而且我們想到這種方法的時候,Spring早就提供給我們接口了:
public abstract class WebApplicationContextUtils { public static WebApplicationContext getRequiredWebApplicationContext(ServletContext sc) throws IllegalStateException { WebApplicationContext wac = getWebApplicationContext(sc); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?"); } return wac; }
getWebApplicationContext方法如下:
public static WebApplicationContext getWebApplicationContext(ServletContext sc) { return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); }
第二種方法:
前面說到Spring初始化的時候,將ApplicationContext還存了一份到ContextLoader的Map裏面,那麽我們是不是可以通過Map.get(key) ???很不幸的是,這個Map是私有的。
private static final Map currentContextPerThread = CollectionFactory.createConcurrentMapIfPossible(1);
Spring為我們提供的方法:
public static WebApplicationContext getCurrentWebApplicationContext() { return (WebApplicationContext) currentContextPerThread.get(Thread.currentThread().getContextClassLoader()); }
第二種方法與第一種方法相比有什麽好的地方呢?就是它不需要參數,只要在Web容器中,當Spring初始化之後,你不需要傳入任何參數,就可以獲得ApplicationContext。不過這個方法在Spring2.52版本中是不存在的,但是在2.5.5版本中提供了。
其實第二種獲取方法看上去簡單,但他的原理還是有一定難度的,他與類加載器的線程上下文相關,這個線程上下文在咱們常用的Mysql驅動中有用到。
第三種方式:
借用ApplicationContextAware,ApplicationContext的幫助類能夠自動裝載ApplicationContext,只要你將某個類實現這個接口,並將這個實現類在Spring配置文件中進行配置,Spring會自動幫你進行註入 ApplicationContext.ApplicationContextAware的代碼結構如下:
public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
就這一個接口。可以這樣簡單的實現一個ApplicationContextHelper類:
public class ApplicationHelper implements ApplicationContextAware { private ApplicationContext applicationContext; public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } public ApplicationContext getApplicationContext(){ return this.applicationContext; } }
通過ApplicationHelper我們就可以獲得咱們想要的AppilcationContext類了。
WebApplicationContext的三種初始化方式