Spring源碼情操陶冶-ContextLoader


前言-閱讀源碼有利於陶冶情操,本文承接前文Spring源碼情操陶冶-ContextLoaderListener

靜態代碼塊內容

ContextLoader在被主動調用的時候,會執行其的一個靜態塊,代碼如下

static {
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized
		// by application developers.
		try {
		        //這里DEFAULT_STRATEGIES_PATH值為ContextLoader.properties
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
			//這里的loadProperties方法其實調用的getClass().getResourceAsStream(path)方法
			//其是從當前包路徑下查找相應的資源,點開相應的class路徑,果不其然ContextLoader.properties與ContextLoader.class在同一目錄下
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
		}
	}

具體的解析功能見注釋,上面分析到其會讀取ContextLoader.properties,這個文件下只有一個屬性:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

由此可見其最有可能是通過XmlWebApplicationContext類來進行相應的web上下文初始化

初始化方法initWebApplicationContext

從前文得知,ContextLoaderListener會調用ContextLoader#initWebApplicationContext(ServletContext context)方法來創建web application上下文環境,代碼清單如下

//首先判斷有無org.springframework.web.context.WebApplicationContext.ROOT屬性,不允許已有
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) {
				//創建方法,一般會創建前點所述的XmlWebApplicationContext
				this.context = createWebApplicationContext(servletContext);
			}
			//XmlWebApplicationContext是ConfigurableWebApplicationContext的實現類,指定的contextClass應該是ConfigurableWebApplicationContext的實現類
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				//一般來說剛創建的context並沒有處於激活狀態
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent ->
						// determine parent for root web application context, if any.
						//在web.xml中配置了<context-param>的parentContextKey才會指定父級應用
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					//讀取相應的配置並且刷新context對象
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
                        //設置屬性,避免再被初始化			
                        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			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;
		}

上述的代碼片段中主要用到了兩個關鍵方法createWebApplicationContext(ServletContext context)configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac,ServletContext sc),本文必須對這兩個方法進行相應的解讀

ContextLoader#createWebApplicationContext(ServletContext context)

代碼清單如下

                //決定采用默認的contextClass還是ServletContext中的參數屬性contextClass
                Class<?> contextClass = determineContextClass(sc);
		//這里Spring強調指定的contextClass必須是ConfigurableWebApplicationContext的實現類或者子類
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
		//這里就是實例化指定的contextClass
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

實現的主要功能為創建ConfigurableWebApplicationContext對象,這里可以稍微瞄一眼determineContextClass(ServletContext context)

            //優先從ServletContext取contextClass參數對應的值,即查看是否在web.xml中配置
            //對應的contextClass<context-param>參數值
            String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		if (contextClassName != null) {
			try {
				return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load custom context class [" + contextClassName + "]", ex);
			}
		}
		else {
			//倘若不存在,則加載指定的XmlWebApplicationContext類
			contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
			try {
				return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load default context class [" + contextClassName + "]", ex);
			}
		}

ContextLoader#configureAndRefreshWebApplicationContext()

代碼清單如下

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc){
//一般此處為真
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			// The application context id is still set to its original default value
			// -> assign a more useful id based on available information
			//獲取servletContext中的contextId屬性
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				//存在則設為指定的id名
				wac.setId(idParam);
			}
			else {
				// Generate default id... 一般為org.springframework.web.context.WebApplicationContext:${contextPath}
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}

		wac.setServletContext(sc);
		//讀取contextConfigLocation屬性
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			//設置指定的spring文件所在地,支持classpath前綴並多文件,以,;為分隔符
			wac.setConfigLocation(configLocationParam);
		}

		// The wac environment's #initPropertySources will be called in any case when the context
		// is refreshed; do it eagerly here to ensure servlet property sources are in place for
		// use in any post-processing or initialization that occurs below prior to #refresh
		//獲得真實對象為StandardEnvironment 其非ConfigurableWebEnvironment的實現類
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
		//查看是否有ApplicationContextInitializer<C extends ConfigurableApplicationContext>啟動類,其實是web.xml需要指定globalInitializerClasses參數或者contextInitializerClasses參數,前提是指定的這些類的泛型類必須是wac的父類或者與wac類相同,否則就會有異常拋出
		customizeContext(sc, wac);
		//調用AbstractApplicationContext的refresh方法實現加載spring文件等操作
		wac.refresh();
}

可見關鍵處理還是在AbstractApplicationContext#refresh()方法,前面都是refresh()方法的准備工作,包括指定contextConfigLocationSpring配置文件位置、給應用一個id倘若指定了contextId屬性、如果指定了globalInitializerClasses或者contextInitializerClasses參數則調用其中的initialize()方法

記錄總結

  1. ContextLoader在web.xml配置文件中沒有指定contextClass的屬性下會默認加載XmlWebApplicationContext類,如果指定了contextClass屬性,Spring強調指定的contextClass必須是ConfigurableWebApplicationContext的實現類或者子類

  2. web.xml配置文件中如果沒有指定contextId屬性,則WebApplicationContext的id為org.springframework.web.context.WebApplicationContext:${contextPath},其中${contextPath}是項目的上下文路徑,例如localhost:8080/test/info/query路徑下的contextPath為/test

  3. web.xml配置的contextConfigLocation屬性支持多文件,以,;為分隔符,不指定默認會加載applicationContext.xml,其在后續章節剖析

  4. web.xml倘若指定globalInitializerClasses參數或者contextInitializerClasses參數,具體要求如下
    * 指定的類實現ApplicationContextInitializer<C extends ConfigurableApplicationContext>接口
    * 指定的這些類中的泛型類必須是contextClass(默認為XmlWebApplicationContext)的父類或者一致,否則就會有異常拋出

下節預告

Spring源碼情操陶冶-AbstractApplicationContext


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM