天天看點

2、容器的重新整理

    上一節分析了父容器(ClasspathXmlApplicationContext)的加載,其加載的配置檔案可以在web.xml中配置,但是并沒有仔細的去研究父容器的建立的過程,那是因為父容器的建立與子容器的建立基本一緻。本節将分析ApplicationContext與BeanFactory的重新整理過程。

2.1 配置檔案的解析

    context建立後,接下來就需要準備好配置檔案,以供後面解析配置做準備。

//(0) 配置和重新整理context
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        //底層使用了JDK提供的類名[email protected]+hashCode位址作為唯一id,在調用容器構造器的時候生成
		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
			//通過在web.xml中配置contextId來修改預設的id
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				// Generate default id...
				//如果web.xml中未提供,生成與web相關的id
				//一般情況下就是org.springframework.web.context.WebApplicationContext: + sc.getContextPath())
				//目前這個id,我看見在建構容器關聯的BeanFactory的時候被使用到,用于設定BeanFactory的序列化id
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}

		wac.setServletContext(sc);
//從web.xml中擷取配置檔案路徑
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
//設定配置檔案路徑,這個容器是實作了ConfigurableWebApplicationContext接口,還有PropertyResolver接口,會對這個配置路徑進行占位符的解析,目前能夠擷取到的占位符屬性為System.getProperties(),System.getEnv(),其他的ServletContext的屬性,ServletConfig的屬性都為存根對象,直接傳回null
			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
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
//替換建立ConfigurableEnvironment時設定的存根占位符
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
//調用ApplicationContextInitializer
		customizeContext(sc, wac);
//(1)重新整理容器
		wac.refresh();
	}
           

    上面給建立的context設定唯一的id之後,從web.xml中讀取了配置檔案的路徑,并将配置檔案路徑設定給context,看似是簡單的set方法,實際上spring做了其他的事情,那麼就是對配置檔案路徑進行占位符的解析,替換完占位符才會設定給context,解析完配置檔案路徑之後,會調用實作了ApplicationContextInitializer的初始化方法,最後就是重新整理容器。     這裡還有一個值得注意的是,在設定配置檔案路徑的過程中,為了解析占位符,spring會建立一個StandardServletEnvironment執行個體,這個執行個體具有提供屬性資源和解析占位符的能力,接下來,我們來看看spring是如果解析占位符的。

public void org.springframework.context.support.AbstractRefreshableConfigApplicationContext.setConfigLocations(String... locations) {
		if (locations != null) {
			Assert.noNullElements(locations, "Config locations must not be null");
			this.configLocations = new String[locations.length];
			for (int i = 0; i < locations.length; i++) {
			    //(1)解析占位符
		 this.configLocations[i] = resolvePath(locations[i]).trim();
			}
		}
		else {
			this.configLocations = null;
		}
	}
	
	//(1)解析占位符,那解析後的占位符的屬性值,從哪裡找呢?是以這個時候我們需要建立一個環境執行個體,用于提供屬性值,由于目前context是實作了WebApplicationContext接口,繼而它的實作方法在AbstractRefreshableWebApplicationContext中,是以它所建立的環境變量當然會是與web相關的StandardServletEnvironment
	
	protected ConfigurableEnvironment createEnvironment() {
		return new StandardServletEnvironment();
	}
           

    我們來看看StandardServletEnvironment的類圖

2、容器的重新整理
PropertyResolver(定義根據屬性key擷取屬性值,定義解析占位符功能)
ConfigurablePropertyResolver(定義擷取ConversionService的能力,用于進行屬性值的轉換,ConversionServic,提供轉換能力,ConverterRegistry,注冊轉換器)
Environment(定義擷取預設激活配置檔案profile的能力)
ConfigurableEnvironment(設定激活配置檔案,以及擷取配置檔案的能力)
AbstractEnvironment(實作了解析占位符的能力,持有PropertySourcesPropertyResolver(用于解析占位符),MutablePropertySources(提供屬性值,内部采用組合模式)執行個體對象)
StandardEnvironment(customizePropertySources)
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
//内部使用System.getProperty()
   propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
//内部使用System.getEnv()
   propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
           
StandardServletEnvironment(customizePropertySources)
protected void customizePropertySources(MutablePropertySources propertySources) {
//設定servletConfigInitParams存根,其實就是起占位符的能力  
 propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
//servletContextInitParams
   propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
//jndi占位符jndiProperties
   if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
      propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
   }
//這個就是上面那個方法
   super.customizePropertySources(propertySources);
}
           

    從上面的繼承結構我們大緻了解了StandardServletEnvironment具有提供屬性值和解析替換占位符的能力,那麼它的屬性資源從哪裡來呢?其中有部分屬性資源在構造它的執行個體的時候就會預設添加,具體代碼如下:

protected void org.springframework.web.context.support.StandardServletEnvironment.customizePropertySources(MutablePropertySources propertySources) {
        //添加servletConfig存根對象,不提供屬性值,調用時傳回null(所謂存根對象,相當于占位符,後期會進行替換,為啥要占着茅坑不拉屎呢?你想象一下停車位就知道了,你要放個牌子,告訴别人這是我的,如果你不放就會被人霸占。)
		propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
			propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
		}
		//調用父類的customizePropertySources方法super.customizePropertySources(propertySources);這裡我将父類的方法内聯進來了
		//添加系統屬性變量
		propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		
		//添加系統環境變量
		propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
		
		
	}

           

    以上通過MutablePropertySources添加了多個屬性源,當所需要的屬性源準備就緒之後就可以進行占位符的解析了

protected String resolvePath(String path) {
        //解析占位符
		return getEnvironment().resolveRequiredPlaceholders(path);
	}
	
	
	
	public String 	org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
	    //建立占位符解析幫助類,這個類主要用于占位符的解析,而目前執行個體是PropertySourcesPropertyResolver類的執行個體,這個類主要負責屬性值的擷取
		if (this.strictHelper == null) {
			this.strictHelper = createPlaceholderHelper(false);
		}
		//解析占位符,将解析後的占位符到屬性資源中擷取屬性值,然後替換之後傳回
		return doResolvePlaceholders(text, this.strictHelper);
	}
	
	
	//解析占位符
	protected String parseStringValue(
			String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

		StringBuilder result = new StringBuilder(strVal);
        //擷取${開始位置
		int startIndex = strVal.indexOf(this.placeholderPrefix);
		while (startIndex != -1) {
		    //擷取最終與${配對的},因為可能存在這樣的${${spring.profil.action}:dev}
		    //這裡的${應當比對:dev後面這個}
			int endIndex = findPlaceholderEndIndex(result, startIndex);
			if (endIndex != -1) {
				String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
				String originalPlaceholder = placeholder;
				//不允許循環引用,否則一直遞歸,導緻Stack Overflow
				if (!visitedPlaceholders.add(originalPlaceholder)) {
					throw new IllegalArgumentException(
							"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
				}
				// Recursive invocation, parsing placeholders contained in the placeholder key.
				//遞歸解析占位符
				placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
				// Now obtain the value for the fully resolved key...
				//擷取占位符的值
				String propVal = placeholderResolver.resolvePlaceholder(placeholder);
				if (propVal == null && this.valueSeparator != null) {
				   //如果沒有擷取到占位符,那麼擷取預設值,比如${${spring.profil.action}:dev},預設值是dev
					int separatorIndex = placeholder.indexOf(this.valueSeparator);
					if (separatorIndex != -1) {
						String actualPlaceholder = placeholder.substring(0, separatorIndex);
						String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
						propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
						if (propVal == null) {
							propVal = defaultValue;
						}
					}
				}
				if (propVal != null) {
					// Recursive invocation, parsing placeholders contained in the
					// previously resolved placeholder value.
					//遞歸解析屬性值的占位符
					propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
					result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
					if (logger.isTraceEnabled()) {
						logger.trace("Resolved placeholder '" + placeholder + "'");
					}
					startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
				}
				else if (this.ignoreUnresolvablePlaceholders) {
					// Proceed with unprocessed value.
					startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
				}
				else {
					throw new IllegalArgumentException("Could not resolve placeholder '" +
							placeholder + "'" + " in string value \"" + strVal + "\"");
				}
				visitedPlaceholders.remove(originalPlaceholder);
			}
			else {
				startIndex = -1;
			}
		}

		return result.toString();
	}
	
	
           

    下面是解析占位符的序列圖

2、容器的重新整理

2.2 context的重新整理

public void org.springframework.context.support.AbstractApplicationContext.refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
//(2)設定context啟動時間,設定激活标記,設定是否關閉标記,并且初始化資源,這裡會提供初始化屬性資源的機會,并對屬性資源的校驗機會
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
//(3)建立context關聯的BeanFactory,這個建立的是DefaultListableBeanFactory,重新整理容器,加載配置檔案

	ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			。。。。。。
	}



//(2)
protected void org.springframework.context.support.AbstractApplicationContext.prepareRefresh() {
//設定啟動時間
		this.startupDate = System.currentTimeMillis();
//設定關閉flag
		this.closed.set(false);
//設定激活flag
		this.active.set(true);

		if (logger.isInfoEnabled()) {
			logger.info("Refreshing " + this);
		}

		// Initialize any placeholder property sources in the context environment
//我們可以繼承這個類,然後設定自己的propertSource,自己重新設定配置檔案位置,甚至解析占位符。縱觀以前的運作流程,我們可以更改設定父容器的能力,id,環境等等資訊,甚至我們後面可以修改建立BeanFactory的方法等方式。
		initPropertySources();

		// Validate that all properties marked as required are resolvable
		// see ConfigurablePropertyResolver#setRequiredProperties
//校驗一些必填屬性
		getEnvironment().validateRequiredProperties();

		// Allow for the collection of early ApplicationEvents,
		// to be published once the multicaster is available...
//這裡可以設定餓漢事件,一旦事件廣播可用,就會觸發對應的事件
		this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
	}


//(3)這個方法内部調用了這個方法
protected final void org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory() throws BeansException {
//如果已經存在BeanFactory,那麼就進行銷毀,主要用于重新啟動容器的時候
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
//建立BeanFactory
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
//設定BeanFactory是否允許BeanDefinition的覆寫,是否允許循環依賴
			customizeBeanFactory(beanFactory);
//(4)建立XmlBeanDefinitionReader,設定BeanFactory到XmlBeanDefinitionReader等,準備加載BeanDefinition
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}


//(4)
protected void org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
//設定環境屬性,用于配置檔案中占位符的解析
		beanDefinitionReader.setEnvironment(getEnvironment());
//設定配置資源加載器,由于context實作了ResourceLoader接口,context是有加載資源的能力的
		beanDefinitionReader.setResourceLoader(this);
//解析器,用于校驗配置檔案的正确性
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
//鈎子函數,提供給子類實作
		initBeanDefinitionReader(beanDefinitionReader);
//使用reader加載解析配置檔案
		loadBeanDefinitions(beanDefinitionReader);
	}
           

    上面這段代碼上面的注釋非常明确,這裡不多贅述,主要是看看DefaultListableBeanFactory,context應用了這個BeanFactory的執行個體,那麼它具有什麼樣的能力,我們來看下它的類圖

2、容器的重新整理
AliasRegistry(提供别名注冊,移除的功能)
BeanDefinitionRegistry(提供BeanDefinition注冊能力)
SimpleAliasRegistry(提供别名注冊,移除的實作能力)
DefaultSingletonBeanRegistry(提供單例bean的注冊,擷取)
FactoryBeanRegistrySupport(定義如何擷取工廠bean與注冊工廠bean到緩存中)
AbstractBeanFactory(主要提供擷取bean的模闆方法(createBean))
AbstractAutowireCapableBeanFactory(具有自動裝配能力以及BeanFactoryPostProccess擴充能力)
SingletonBeanRegistry(注冊單例bean,擷取單例bean)
BeanFactory(定義擷取bean的能力)
HierarchicalBeanFactory(定義擷取父BeanFactory的能力)
ConfigurableBeanFactory(提供配置功能,定義擷取轉換器的能力)
AutowireCapableBeanFactory(定義自動裝配的功能)
ListableBeanFactory(定義可枚舉bean的能力)
ConfigurableListableBeanFactory(定義了提前初始化非懶加載bean)
DefaultListableBeanFactory(提供具體的枚舉bean的能力)

繼續閱讀