1. 程式人生 > >詳解contextConfigLocation|Spring啟動過程詳解

詳解contextConfigLocation|Spring啟動過程詳解



spring的應用初始化流程一直沒有搞明白,剛剛又碰到了相關的問題。決定得好好看看這個流程。我們在開發spring的專案當中基本上都會在web.xml通過:

複製程式碼
<context-param>  
        <param-name>contextConfigLocation</param-name>  
        <param-value>  
        /WEB-INF/conf/application-*.xml  
        </param-value>  
    </context-param>  
複製程式碼

來初始化各個spring的配置檔案,但是我們只是知道這段程式碼的功能, 並不是很清楚我們配置了這段程式碼之後為什麼就能去初始化配置檔案。當然我們還會加上:

<listener>  
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
    </listener>  

這一個listener,我首先就會想contextConfigLocation這個一定能在ContextLoaderListener這個類當中找到,打開了原始碼,這個listener是實現了ServletContextListener這個介面的,這個介面只有兩個方法:

複製程式碼
public interface ServletContextListener  
    extends EventListener  
{  
  
    public abstract void contextInitialized(ServletContextEvent servletcontextevent);  
  
    public abstract void contextDestroyed(ServletContextEvent servletcontextevent);  
}  
複製程式碼

而且它是繼承了EventListener這個介面的,開啟這個介面的程式碼讓我大吃一驚,裡面沒有方法啥都沒有:

複製程式碼
package java.util;  
  
  
public interface EventListener  
{  
}  
複製程式碼

而且還是java.util包下的,並不是spring之中的東西。

這樣找了之後沒有找到,往回退到ContextLoaderListener這個類的方法上,contextInitialized方法是用來初始化上下文的:

public void contextInitialized(ServletContextEvent event)  
    {  
        contextLoader = createContextLoader();  
        contextLoader.initWebApplicationContext(event.getServletContext());  
    }  

方法中有個createContextLoader方法:

protected ContextLoader createContextLoader()  
    {  
        return new ContextLoader();  
    }  

這個方法返回了一個ContextLoader例項,進入到ContextLoader類中,按ctrl+f來尋找contextConfigLocation,這時沒有出現電腦的咚的聲音,找到了它:

複製程式碼
protected WebApplicationContext createWebApplicationContext(ServletContext servletContext, ApplicationContext parent)  
        throws BeansException  
    {  
        Class contextClass = determineContextClass(servletContext);  
        if(!(org.springframework.web.context.ConfigurableWebApplicationContext.class).isAssignableFrom(contextClass))  
        {  
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + (org.springframework.web.context.ConfigurableWebApplicationContext.class).getName() + "]");  
        } else  
        {  
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);  
            wac.setParent(parent);  
            wac.setServletContext(servletContext);  
            wac.setConfigLocation(servletContext.getInitParameter("<span style="color:#ff0000;">contextConfigLocation</span>"));  
            customizeContext(servletContext, wac);  
            wac.refresh();  
            return wac;  
        }  
    }  
複製程式碼

通過程式碼,ConfigurableWebApplicationContext設定了從servletContext獲取到的引數的值,再進入ConfigurableWebApplicationContext的程式碼中,它只是一個介面,進入StaticWebApplicationContext的setConfigLocation方法:

複製程式碼
public void setConfigLocation(String configLocation)  
    {  
        if(configLocation != null)  
            throw new UnsupportedOperationException("StaticWebApplicationContext does not support config locations");  
        else  
            return;  
    }  
複製程式碼

這個方法中很奇怪,當引數不為空就丟擲異常,檢視spring的文件:The StaticWebApplicationContext class does not support this method.說是此類不支援這個方法,這下子又卡住了。又要退回去,看這句:

ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);  

pring使用BeanUtils來初始化contextClass這個類例項,contextClass是通過以下程式碼得到的:

複製程式碼
protected Class determineContextClass(ServletContext servletContext)  
        throws ApplicationContextException  
    {  
        String contextClassName = servletContext.getInitParameter("contextClass");  
        if(contextClassName != null)  
            try  
            {  
                return ClassUtils.forName(contextClassName);  
            }  
            catch(ClassNotFoundException ex)  
            {  
                throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex);  
            }  
        contextClassName = defaultStrategies.getProperty((org.springframework.web.context.WebApplicationContext.class).getName());  
        try  
        {  
            return ClassUtils.forName(contextClassName, (org.springframework.web.context.ContextLoader.class).getClassLoader());  
        }  
        catch(ClassNotFoundException ex)  
        {  
            throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex);  
        }  
    }  
複製程式碼

這裡使用了反射,再來看BeanUtils的instantiateClass方法:

return instantiateClass(clazz.getDeclaredConstructor((Class[])null), null);  

通過反射得到contextClass的構造方法。下面是instantiateClass方法的過載,主要是下面兩句程式碼:

ReflectionUtils.makeAccessible(ctor);  
            return ctor.newInstance(args);  

ctor是通過反射得到的contextClass的構造方法,args是構造方法當中的引數。這裡為null,說明new了contextClass的無參構造方法。

這時又要退回到determineContextClass 這個方法中,我們主要看:

contextClassName = defaultStrategies.getProperty((org.springframework.web.context.WebApplicationContext.class).getName());  

這句程式碼,我們可以猜它是通過Properties的getProperty方法得到WebApplicationContext 的例項,這時我們又到了WebApplicationContext 這個介面當中,這個介面繼承了ApplicationContext這個介面,我們都知道我們進行spring開發都會通過Application ctx=new FileSystemXmlApplicationContext("beans.xml");或ApplicationContext ctx=new ClassPathXmlApplicationContext("beans.xml");或ServletContext servletContext = request.getSession().getServletContext();ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);這三種方法獲得一個ApplicationContext,然後就可以對配置檔案當中的bean進行操作了。所以這裡我們基本上已經搞清楚初始化spring配置檔案的流程了。

總結:通過檢視這幾個類的原始碼,java的反射使用範圍之廣再次體現出來。如看了之後覺得有錯誤或者不同意見,歡迎提出來,我也是第一次才研究這個問題。