Spring源碼閱讀之WebapplicationContext初始化

Spring源碼閱讀之WebapplicationContext初始化

​ 當一個Web應用在服務器(tomcat)中啓動的時,tomcat需要給該應用創建一個ServletContext對象作爲公共容器保存了應用中的配置信息。這些配置信息是通過讀取web.xml文件中的標籤來獲取的。

​ 應用在tomcat中啓動時,首先會讀取web.xml中兩個標籤節點<context-param>、<listener>並創建ServletContext對象,將context-param標籤中配置文件的位置通過鍵值對的方式存入該對象中。以後可以通過getInitParameter("")的方式來獲取(webapplicationContext的初始化/上下文環境的刷新正是使用這種方法來獲取配置文件)

從WebApplicationContext中可以獲得ServletContext的引用,整個Spring應用上下文對象將作爲屬性放置到ServletContext中,以便Web應用環境可以訪問Sping應用上下文。
<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring/applicationContext*.xml</param-value>
</context-param>
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

​ 全局的ServletContext對象創建完成之後,接下來就需要對Spring的IOC容器進行初始化,並配置其上下文環境對象WebApplicationContext

​ 將配置文件的信息存入ServletContext對象後,接着讀取listener標籤並創建ContextLoaderListener對象,該對象繼承了ContextLoader和實現了ServletContextListener接口。ServletContextListener是ServletContext的監聽者,用來監聽對象的創建和銷燬,並執行相應的方法。

ContextLoaderListener的作用就是讀取配置文件的信息並存入webApplicationContext對象中。由於繼承自ContextLoader,所以這一過程由ContextLoader完成。

源碼解釋:

ContextLoaderListener對象創建的時候會執行contextInitialized來完成初始化工作,具體的初始化流程在ContextLoader中的initWebApplicationContext方法。

public void contextInitialized(ServletContextEvent event) {
     this.initWebApplicationContext(event.getServletContext());
}

initWebApplicationContext創建並初始化Context對象。

private WebApplicationContext context;
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //首先判斷該sercontext對象中是否已經有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!");
        } else {
            try {
                if (this.context == null) {
                    //如果WebApplicationContext沒有被初始化過,通過該方法創建該對象。
                    this.context = this.createWebApplicationContext(servletContext);
                }
                /*如果創建的WebApplicationContext對象是ConfigurableWebApplicationContext的一個實例(有繼承關係)。
                那麼就會將創建的對象強制轉型爲ConfigurableWebApplicationContext*/
                if (this.context instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                 	//對象是否處於活躍狀態
                    if (!cwac.isActive()) {
                        //是否有父環境對象
                        if (cwac.getParent() == null) {
                            //將ServletContext設置爲父環境對象
                            ApplicationContext parent = this.loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }
                        //通過servletContext中的配置信息來刷新ConfigurableWebApplicationContext對象環境
                        this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }
                //將創建並初始化完成的WebApplicationContext對象保存進ServletContext中。
        	servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
           
        }	
}

​ 首先看是如何創建的,並且創建的具體對象是什麼。通過defaultStrategies對象來得知實際創建的類名稱,並實例化該類,該對象在靜態代碼塊中初始化的,並通過加載ContextLoader.properties文件完成初始化過程。最後實際創建的對象爲XmlWebApplicationContext

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    	//通過determineContextClass來得到具體類
        Class<?> contextClass = this.determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        } else {
            return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        }
}

protected Class<?> determineContextClass(ServletContext servletContext) {
    //讀取ServletContext中的contextClass的值。一般來說web.xml並不會配置該信息。
        String contextClassName = servletContext.getInitParameter("contextClass");
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            } catch (ClassNotFoundException var4) {
                throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
            }
        } else {
            //實際上會通過defaultStrategies來得到具體創建的類名。
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());

            try {
                //通過類的名稱得到該類Class對象。
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            } catch (ClassNotFoundException var5) {
                throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
            }
        }
    }
//通過靜態代碼塊來初始化defaultStrategies對象。
static {
        try {
            //加載資源文件ContextLoader.properties(相對路徑) 
            ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
            //通過配置文件類初始化defaultStrategies對象。
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        } catch (IOException var1) {
            throw new IllegalStateException("Could not load 'ContextLoader.properties': " + var1.getMessage());
        }

        currentContextPerThread = new ConcurrentHashMap(1);
    }


//ContextLoader.properties文件內容:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
    
    
//可以看到XmlWebApplicationContext設置了一些默認的值,如果在web.xml中沒有指定<context-param>標籤那麼默認會讀取該類中指定的文件。
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
    public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
    public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
    public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
}

​ 實際的創建過程瞭解之後,context對象如何讀取我們指定的配置文件的呢?

​ 首先了解一下webApplicationContenxt的繼承關係:

在這裏插入圖片描述

​ 通過圖片得知XmlWebApplicationContext繼承AbstractRefreshableWebApplicationContextAbstractRefreshableWebApplicationContext實現ConfigurableWebApplicationContext接口,該接口繼承了WebApplicationContext接口。

​ 再回到initWebApplicationContext中這段代碼:

if (this.context instanceof ConfigurableWebApplicationContext) {
			/*如果創建的WebApplicationContext對象是ConfigurableWebApplicationContext的一個實例(有繼承關係)。
                那麼就會將創建的對象強制轉型爲ConfigurableWebApplicationContext*/
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                 	//對象是否處於活躍狀態
                    if (!cwac.isActive()) {
                        //是否有父環境對象
                        if (cwac.getParent() == null) {
                            //將ServletContext設置爲父環境對象
                            ApplicationContext parent = this.loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }
                        //通過servletContext中的配置信息來刷新ConfigurableWebApplicationContext對象環境
                        this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }
                //將創建並初始化完成的WebApplicationContext對象保存進ServletContext中。
        	servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

​ 通過創建過程瞭解到實際創建的是XmlWebApplicationContext對象,該對象和ConfigurableWebApplicationContext是存在繼承關係的。因此這裏會發生強制轉型,通過configureAndRefreshWebApplicationContext來初始化或者刷新context對象的上下文環境。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        String configLocationParam;
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            configLocationParam = sc.getInitParameter("contextId");
            if (configLocationParam != null) {
                wac.setId(configLocationParam);
            } else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }
		//設置ServletContext環境。
        wac.setServletContext(sc);
    	/*讀取Servlet對象中的contextConfigLocation.  ServletContext在創建的時候就讀取了
    		<context-param>標籤,並將該標籤中的配置文件的信息通過鍵值對的形式保存進來了。
    	*/
        configLocationParam = sc.getInitParameter("contextConfigLocation");
        if (configLocationParam != null) {
            //保存配置文件信息的位置。
            wac.setConfigLocation(configLocationParam);
        }
		//刷新環境
        wac.refresh();
    }

​ 讀取servletContext對象中contextConfigLocation的值(配置文件的位置)保存進configureAndRefreshWebApplicationContext對象中,並刷新上下文環境。

​ SpringIOC容器先根據監聽初始化WebApplicationContext,然後再初始化web.xml中其他配置的servlet(DispatcherServlet),爲其初始化自己的上下文信息servletContext,並加載其設置的配置信息和參數信息到該上下文中,將WebApplicationContext設置爲它的父容器。所以最後的關係是ServletContext包含了WebApplicationContext,WebApplicationContext包含了其他的Servlet上下文環境。(DispatcherServlet),爲其初始化自己的上下文信息servletContext,並加載其設置的配置信息和參數信息到該上下文中,將WebApplicationContext設置爲它的父容器。所以最後的關係是ServletContext包含了WebApplicationContext,WebApplicationContext包含了其他的Servlet上下文環境。

參考:https://www.jianshu.com/p/f8d560962a68

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章