1. 程式人生 > >容器的功能擴充套件(一)

容器的功能擴充套件(一)

我們前面一直以BeanFactory介面以及它的預設實現類XmlBeanFactory為例進行分析,但是Spring中還提供了另一個介面ApplicationContext,用於擴充套件BeanFactory中現有的功能。

ApplicationContext和BeanFactory兩者都是用於載入Bean的,但是相比之下,ApplicationContext提供了更多的擴充套件功能,簡而言之:ApplicationContext包含BeanFactory的所有功能。通常建議比BeanFactory優先,除非在一些限制的場合,比如位元組長度對記憶體有很大的影響時(Applet).絕大多數“典型的”企業應用系統,ApplicationContext就是需要使用的。

那麼究竟ApplicationContext比BeanFactory多了哪些功能?首先我們來看看使用兩個不同的類去載入配置檔案在寫法上的不同。

  • 使用BeanFactory方式載入XML.


BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));

  • 使用ApplicationContext方式載入XML.

ApplicationContext bf = new ClassPathXmlApplicationContext("beanFactoryTest.xml");

我們以ClassPathXmlApplicationContext作為切入點,開始對整體功能進行分析。

	public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
	}

	public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
			throws BeansException {

		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}

設定路徑是必不可少的步驟,ClassPathXmlApplicationContext中可以將配置檔案路徑以陣列的方式傳入,ClassPathXmlApplicationContext可以對陣列進行解析並進行載入。而對於解析及功能實現都在refresh()中實現。

1.設定配置路徑

在ClassPathXmlApplicationContext中支援多個配置檔案以陣列方式同時傳入:

public void 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++) {
				this.configLocations[i] = resolvePath(locations[i]).trim();
			}
		}
		else {
			this.configLocations = null;
		}
	}

2.擴充套件功能

設定了路徑之後,便可以根據路徑做配置檔案的解析以及各種功能的實現了。可以說refresh函式中包含了幾乎ApplicationContext中提供的全部功能,而且此函式中邏輯非常清晰明瞭,使我們很容易分析對應的層次及邏輯

	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			//準備重新整理的上下文 環境
			/*
			 * 初始化前的準備工作,例如對系統屬性或者環境變數進行準備及驗證。
			 * 在某種情況下專案的使用需要讀取某些系統變數,而這個變數的設定很可能會影響著系統
			 * 的正確性,那麼ClassPathXmlApplicationContext為我們提供的這個準備函式就顯得非常必要,
			 * 它可以在Spring啟動的時候提前對必須的變數進行存在性驗證。
			 */
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			//初始化BeanFactory,並進行XML檔案讀取
			/*
			 * ClassPathXMLApplicationContext包含著BeanFactory所提供的一切特徵,在這一步驟中將會複用
			 * BeanFactory中的配置檔案讀取解析及其他功能,這一步之後,ClassPathXmlApplicationContext
			 * 實際上就已經包含了BeanFactory所提供的功能,也就是可以進行Bean的提取等基礎操作了。
			 */
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			//對beanFactory進行各種功能填充
			/*
			 * @Qualifier與@Autowired等註解正是在這一步驟中增加的支援
			 */
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				//子類覆蓋方法做額外處理
				/*
				 * Spring之所以強大,為世人所推崇,除了它功能上為大家提供了便利外,還有一方面是它的
				 * 完美架構,開放式的架構讓g使用它的程式猿很容易根據業務需要擴充套件已經存在的功能。這種開放式
				 * 的設計在Spring中隨處可見,例如在本例中就提供了一個空的函式實現postProcessBeanFactory來
				 * 方便程式猿在業務上做進一步擴充套件
				 */
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				//啟用各種beanFactory處理器
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				//註冊攔截Bean建立的Bean處理器,這裡只是註冊,真正的呼叫實在getBean時候
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				//為上下文初始化Message源,即不同語言的訊息提,國際化處理
				initMessageSource();

				// Initialize event multicaster for this context.
				//初始化應用訊息廣播器,並放入“applicationEventMulticaster”bean中
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				//留給子類來初始化其它的Bean
				onRefresh();

				// Check for listener beans and register them.
				//在所有註冊的bean中查詢Listener bean,註冊到訊息廣播器中
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				//初始化剩下的單例項(非惰性的)
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				//完成重新整理過程,通知生命週期處理器lifecycleProcessor重新整理過程,同時發出
				//ContextRefreshEvent通知別人
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}
		}
	}

3.環境準備

prepareRefresh函式主要是做些準備工作,例如對系統屬性及環境變數的初始化及驗證

	protected void prepareRefresh() {
		this.startupDate = System.currentTimeMillis();

		synchronized (this.activeMonitor) {
			this.active = true;
		}

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

		// Initialize any placeholder property sources in the context environment
		//留給子類覆蓋
		initPropertySources();

		// Validate that all properties marked as required are resolvable
		// see ConfigurablePropertyResolver#setRequiredProperties
		getEnvironment().validateRequiredProperties();
	}

這個函式看似沒有社麼用,因為最後兩句程式碼才是關鍵,但是卻沒有什麼邏輯處理,initPropertySources是空的,沒有什麼邏輯,而getEnvironment().validaterequiredProperties也因為沒有需要驗證的屬性而沒有做任何處理。其實這個函式用好了作用還是挺大的,那麼該怎麼用呢,我們先探索下各個函式的作用。

(1)initPropertySources證符合Spring的開放式結構設計,給使用者最大擴充套件Spring的能力。使用者可以根據自身的需要重寫initPropertySourece方法,並在方法中進行個性化的屬性處理及設定。

(2)validateRequiredProperties則是對屬性進行驗證,那麼如何驗證呢?舉個融合兩句程式碼的小例子來理解。

例如現在有這樣一個需求,工程在執行過程中用到的某個設定(例如VAR)是從系統環境變數中取得的,而如果使用者沒有在系統環境變數中配置這個引數,,工程不會工作。這一要求也許有各種各樣的解決辦法,在Spring中可以這麼做,可以直接修改Spring的原始碼,例如修改ClassPathXmlApplicationContext.淡然,最好的辦法是對原始碼進行擴充套件,可以自定義類:

public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext{
      public MyClassPathXmlApplicationContext(String.. configLocations){
            super(configLocations);
       }
       protected void initPropertySources(){
             //新增驗證要求
             getEnvironment().setRequiredProterties("VAR");
       }
}

自定義了繼承自ClassPathXmlApplicationContext的MyClassPathXmlApplicationContext,並重寫了initPropertySources方法,在方法中添加了個性化需求,那麼在驗證的時候也就是程式走到getEnvironment().validateRequiredProperties()程式碼的時候,如果系統並沒有檢測到對應VAR的環境變數,將丟擲異常。當然我們還需要在使用的時候替換掉原有的ClassPathXmlApplicationContext:
public static void main(Stirng[] args){
      ApplicationContext bf = new MyClassPathXmlApplicationContext("myTest.xml");
      User user = (User)bf.getBean("testBean");
}
4.載入BeanFactory

obtainFreshBeanFactory方法從字面理解是獲取beanFactory.ApplicationContext是對BeanFactory的擴充套件,在其基礎上添加了大量的基礎應用,obtainFreshBeanFactory正式實現beanFactory的地方,經過這個函式後ApplicationContext就有了BeanFactory的全部功能。

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		//初始化BeanFactory,並進行XML檔案讀取,並將得到的BeanFactory記錄在當前實體的屬性中
		refreshBeanFactory();
		//返回 當前實體的beanFactory屬性
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (logger.isDebugEnabled()) {
			logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
		}
		return beanFactory;
	}

方法中將核心實現委託給了refreshBeanFactory:
	protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			//建立DefaultListableBeanFactory
			/*
			 * 以前我們分析BeanFactory的時候,不知道是否還有印象,宣告方式為:BeanFactory bf = 
			 * new XmlBeanFactory("beanFactoryTest.xml"),其中的XmlBeanFactory繼承自DefaulltListableBeanFactory;
			 * 並提供了XmlBeanDefinitionReader型別的reader屬性,也就是說DefaultListableBeanFactory是容器的基礎。必須
			 * 首先要例項化。
			 */
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			//為了序列化指定id,如果需要的話,讓這個BeanFactory從id反序列化到BeanFactory物件
			beanFactory.setSerializationId(getId());
			//定製beanFactory,設定相關屬性,包括是否允許覆蓋同名稱的不同定義的物件以及迴圈依賴以及設定
			//@Autowired和Qualifier註解解析器QualifierAnnotationAutowireCandidateResolver
			customizeBeanFactory(beanFactory);
			//載入BeanDefiniton
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				//使用全域性變數記錄BeanFactory例項。
				//因為DefaultListableBeanFactory型別的變數beanFactory是函式內部的區域性變數,
				//所以要使用全域性變數記錄解析結果
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

1)定製BeanFactory

customizeBeanFactor方法在基本容器的基礎上,增加了是否允許覆蓋是否允許擴充套件的設定並提供了主機@Qualifierh額@Autowired支援。

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
		//如果屬性sllowBeanDefinitionOverring不為空,設定給beanFactory物件相應屬性,
		//此屬性的含義:是否允許覆蓋同名稱的不同定義的物件
		if (this.allowBeanDefinitionOverriding != null) {
			beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		//如果屬性allowCircularReferences不為空,設定給beanFactory物件相應屬性,
		//此屬性的含義:是否允許迴圈依賴
		if (this.allowCircularReferences != null) {
			beanFactory.setAllowCircularReferences(this.allowCircularReferences);
		}
		//用於@Qualifier和@Autowired
		beanFactory.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
	}

對於允許覆蓋和允許依賴的設定這裡只判斷了是否為空,如果不為空姚進行設定,但是並沒有看到在哪裡設定,究竟在哪裡設定?

這時候還是利用Spring的可擴充套件性,使用子類覆蓋的方法,例如:

public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext{
      ... ...
      protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory){
            super.setAllowBeanDefinitionOverriding(fa;se);
            super.setAllowCircularReferences(false);
            super.customizeBeanFactory(beanFactory);
      }
}

Spring使用了QualifierAnnotationAutowireCandidateResolver這個解析器後,Spring就支援註解方式的注入了,

在以前分析根據型別注入的時候,我們說過解析autowire型別時首先會呼叫方法:

Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);

因此,在QualifierAnnotationAutowireCandidatResolver中一定會提供解析Autowire註解的方法:

	public Object getSuggestedValue(DependencyDescriptor descriptor) {
		Object value = findValue(descriptor.getAnnotations());
		if (value == null) {
			MethodParameter methodParam = descriptor.getMethodParameter();
			if (methodParam != null) {
				value = findValue(methodParam.getMethodAnnotations());
			}
		}
		return value;
	}

載入beanDefinition

在第一步中提到了將ClassPathXmlApplicationContext與XMLBeanFactory建立的對比,除了初始化DefaultListableBeanFactory外,還需要XmlBeanDefinitionReader來讀取XML,那麼在loadBeanDefinitions方法中首先要做的就是初始化XmlBeanDefinitonReader.

	protected void 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(this.getEnvironment());
		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);
		loadBeanDefinitions(beanDefinitionReader);
	}

在初始化了DefaultListableBeanFactory和XmlBeanDefinitionReader後,就可以進行配置檔案的讀取了。
	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			reader.loadBeanDefinitions(configLocations);
		}
	}

因為在XmlBeanDefinitionReader中已經將之前初始化的DefaultListableBeanFactory註冊進去了,所以XmlBeanDefinitionReader所讀取的BeanDefinitionHolder都會註冊到DefinitionListableBeanFactory中,也就是經過這個步驟,DefaultListableBeanFactory的變數beanFactory已經包含了所有解析好的配置。