加載WebApplicationContext的方式
WebApplicationContext是ApplicationContext的子接口,縱觀Spring框架的幾種容器,BeanFactory作為頂級的接口,是所有IOC容器的最上層接口,顧名思義WebApplicationContext是依賴於Web容器的一個Spring的IOC容器。前提條件是web容器啟動后這個容器才能啟動。那么如何借助web容器來啟動Spring web的上下文?
第一種方式:我們可以通過org.springframework.web.context.ContextLoaderServlet
;第二種式:org.springframework.web.context.ContextLoaderListener.
這兩種方式有什么不同呢?listener完全是觀察者的設計,僅僅執行的是監聽的任務,而servlet的啟動要稍微延遲一些,啟動的前后順序是有影像的。所以我認為listener更好用一些,實際開發中的框架配置中也是listener更多一些,這一點大家應該有所體會。
配置如下:
1 <context-param> 2 <param-name>contextConfigLocation</param-name> 3 <param-value>/WEB-INF/applicationContext.xml</param-value> 4 </context-param> 5 6 <listener> 7 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 8 </listener>
上面那一種是Spring Web容器放在classpath下的任何路徑的配置,如果是放在web.xml約定的配置,則可以省略context-param的配置
通過查看ContextLoaderListener的源代碼可以發現它的類的結構如下圖所示:
通過上圖可以發現ContextLoaderListener繼承了ContextLoader類以及實現了ServletContextListener接口,ServletContextListener又實現了EvenListener接口,所以這個監聽器具有事件監聽的功能,並且是監聽了web容器,web容器啟動馬上加載執行。ContextLoader感覺更像是執行加載web容器的一個小小的core組件,負責執行加載web容器的邏輯。下面重點來說一說這個。
public void contextInitialized(ServletContextEvent event) {
//首先先獲取ContextLoader對象 this.contextLoader = createContextLoader(); if (this.contextLoader == null) { this.contextLoader = this; }
//通過servletContext對象去加載WebApplicationContext this.contextLoader.initWebApplicationContext(event.getServletContext()); }
initWebApplicationContext的主要代碼:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//去servlet容器中去尋找org.springframework.web.context.WebApplicationContext.root作為key的value,也就是webApplicationContext對象
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) {
configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);
}
//將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()即當前線程類加載器
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;
}
}
從上面的代碼大家應該明白了Spring初始化之后,將ApplicationContext存到在了兩個地方(servletContext中和currentContextPerThread中),那么是不是意味着我們可以通過兩種方式取得ApplicationContext?
第一種獲取方式:
1 request.getSession().getServletContext().getAttribute("org.springframework.web.context.WebApplicationContext.ROOT")
這樣確實可以獲取,但是如果需要我們自己去這樣獲取的話,未免Spring也太low了吧?那樣會辜負Spring作為web開發第一核心框架的地位的。話不多說,其實Spring已經給我們提供接口了:
在WebApplicationContextUtils類中有一個靜態方法:
1 public static WebApplicationContext getRequiredWebApplicationContext(ServletContext sc) 2 throws IllegalStateException { 3 4 WebApplicationContext wac = getWebApplicationContext(sc); 5 if (wac == null) { 6 throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?"); 7 } 8 return wac; 9 }
1 public static WebApplicationContext getWebApplicationContext(ServletContext sc) { 2 return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); 3 }
通過執行上面的方法可以獲取WebApplicationContext對象。
第二種方法:
借用ApplicationContextAware,ApplicationContext的幫助類能夠自動裝載ApplicationContext,只要你將某個類實現這個接口,並將這個實現類在Spring配置文件中進行配置,Spring會自動幫你進行注入 ApplicationContext.ApplicationContextAware的代碼結構如下:
1 public interface ApplicationContextAware { 2 3 void setApplicationContext(ApplicationContext applicationContext) throws BeansException; 4 5 }
就這一個接口。可以這樣簡單的實現一個ApplicationContextHelper類:
1 public class ApplicationHelper implements ApplicationContextAware { 2 3 4 private ApplicationContext applicationContext; 5 6 public void setApplicationContext(ApplicationContext applicationContext) 7 throws BeansException { 8 this.applicationContext = applicationContext; 9 } 10 11 12 public ApplicationContext getApplicationContext(){ 13 return this.applicationContext; 14 } 15 }
通過ApplicationHelper我們就可以獲得咱們想要的AppilcationContext類了。
這是我對如何獲取Spring Web上下文的一個理解。