1. 程式人生 > >WebApplicationContext的三種初始化方式

WebApplicationContext的三種初始化方式

實例 aware web.xml span available HA 而且 util onf

  

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的三種初始化方式