spring IoC容器初始化簡單分析

首先分享一篇文章,對於IoC思想的理解

主題:我對IoC/DI的理解http://www.iteye.com/topic/1122310

    我認爲,裏面最重要的一段話如下:

理解IoC容器問題關鍵:控制的哪些方面被反轉了?

1、誰控制誰?爲什麼叫反轉? ------ IoC容器控制,而以前是應用程序控制,所以叫反轉

2、控制什麼?               ------ 控制應用程序所需要的資源(對象、文件……)

3、爲什麼控制?             ------ 解耦組件之間的關係

4、控制的哪些方面被反轉了? ------ 程序的控制權發生了反轉:從應用程序轉移到了IoC容器

下面是我從11月下旬開始,看《Spring 技術內幕》和結合spring源碼的一些簡單分析,時間緊,就比較粗糙。

1.spring IoC容器核心數據結構

看了看,我認爲BeanDefinition、Resource、BeanFactory、ApplicationContext是IoC容器核心結構。

1.1BeanDefinition

作用:持有bean數據結構,是注入的bean在IoC容器中的抽象

方法列表如下:

getBeanClassName;
getFactoryBeanName;
getScope;
isLazyInit;
isSingleton;
isPrototype;
isAbstract;
isPrimary;
isAutowireCandidate;
getDescription;

與BeanDefinition相關的類:

AbstractBeanDefinition、BeanDefinitionReader

AbstractBeanDefinition是BeanDefinition的一個完整實現

BeanDefinitionReader用於讀取bean的信息的接口,AbstractBeanDefinitionReader實現了該接口,XmlBeanDefinitionReader繼承了AbstractBeanDefinitionReader。

1.2Resource

對於資源文件的抽象,最終實現了InputStream getInputStream() throwsIOException這個方法

方法列表如下:

exists();
isReadable();
getURL();
getURI();
getFile();
contentLength();
lastModified();
getFilename();
getDescription();

1.3 BeanFactory

獲取bean的工廠,極其重要,最基本的IoC容器

getBean();
containsBean();
isSingleton();
isPrototype();
isTypeMatch();
getType();
getAliases();

1.4 ApplicationContext

高級IoC容器,除了基本的IoC容器功能外,支持不同信息源、訪問資源、支持事件發佈等功能。

繼承了一下接口

ListableBeanFactory、HierarchicalBeanFactory、MessageSource、ApplicationEventPublisher、ResourcePatternResolver

 

五個接口

接口ListableBeanFactory繼承了BeanFactory,在此基礎之上,添加了containsBeanDefinition、getBeanDefinitionCount、getBeanDefinitionNames等方法。

接口HierarchicalBeanFactory繼承了BeanFactory,在此基礎之上,添加了getParentBeanFactory、containsLocalBean這兩個方法。

MessageSource用於獲取國際化信息。所謂國際化信息,假設我們正在開發一個支持多國語言的Web應用程序,要求系統能夠根據客戶端的系統的語言類型返回對應的界面:英文的操作系統返回英文界面,而中文的操作系統則返回中文界面——這便是典型的i18n國際化問題。對於有國際化要求的應用系統,我們不能簡單地採用硬編碼的方式編寫用戶界面信息、報錯信息等內容,而必須爲這些需要國際化的信息進行特殊處理。簡單來說,就是爲每種語言提供一套相應的資源文件,並以規範化命名的方式保存在特定的目錄中,由系統自動根據客戶端語言選擇適合的資源文件。

ApplicationEventPublisher爲spring的事件發佈者接口。因爲ApplicationContext實現了該接口,因此spring的ApplicationContext實例具有發佈事件的功能。

ResourcePatternResolver:資源裝載器,裝置資源使用。

ApplicationContext對外提供了getId()、getDisplayName()等方法。


2 IoC容器的初始化

IoC容器的初始化就是含有BeanDefinition信息的Resource的定位、載入、解析、註冊四個過程,最終我們配置的bean,以beanDefinition的數據結構存在於IoC容器即內存中。這裏並不涉及bean的依賴注入,不產生任何bean。

2.1BeanDefinition的Resource的定位

在AbstractBeanDefinitionReader.loadBeanDefinitions()方法中,調用了defaultResourceLoader的getResource()方法,這裏即爲resouce的定位,如下

public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");
		if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			return new
ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		else {
			try {
				// Try to parse the location as a URL...
				URL url = new URL(location);
				return new UrlResource(url);
			}
			catch (MalformedURLException ex) {
				// No URL -> resolve as resource path.
				return getResourceByPath(location);
			}
		}
	}

getResourceByPath的實現如下:

protected Resource getResourceByPath(String path) {
		return new ClassPathContextResource(path, getClassLoader());
	}

2.2 beanDefinition的載入

beanDefinition的載入在XmlBeanDefinitionReader中的loadBeanDefinitions()方法實現:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isInfoEnabled()) {
			logger.info("Loading XML bean definitions from " + encodedResource.getResource());
		}
		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<EncodedResource>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

其中,doLoadBeanDefinitions()方法在該類中實現如下

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			int validationMode = getValidationModeForResource(resource);
			Document doc = this.documentLoader.loadDocument(
					inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
			return registerBeanDefinitions(doc, resource);
		}
		catch(){
		//異常處理
}
}

這裏異常拋出了SAXParseException,可以知道,Spring解析xml的工具是SAX。

 

2.3 beanDefinition的解析

beanDefinition的解析是在BeanDefinitionParserDelegate類的parseBeanDefinitionElement(Elementele, BeanDefinition containingBean)方法中完成。

該方法首先調用了


String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

獲取bean的id和bean的name。

接着,調用


AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean)

對bean元素進行詳細解析,調用

parseBeanDefinitionAttributes()解析bean的屬性;

parsePropertyElements(ele, bd)解析bean的property。

bean屬性就是注入scope、isLazyInit、autowire、sigleton等屬性,都是按照類似方法來做:

if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
			// Spring 2.x "scope" attribute
		beanDefinition.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
}

調用beanDefinition的set方法來實現。

解析propterty方法具體在parsePropertyElements()方法中,主要:

1.  bean定義中如果有同名的propterty,那麼只解析第一個property,對於後續的同名propterty不做任何處理。

2. 需要判斷是ref還是value。

2.4 BeanDefinition的註冊

BeanDefinition註冊的意思就是讓IoC知道BeanDefinition是怎麼樣存在的,怎麼樣纔可以拿到BeanDefinition。在DefaultListenerableBeanFactory中,通過一個hashMap<String,BeanDefinition>來操作各個BeanDefinition。

在DefaultListenableBeanFactory中的publicvoid registerBeanDefinition(String beanName,BeanDefinition beanDefinition)throwsBeanDefinitionStoreException

完成了這個步驟。


Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
			if (oldBeanDefinition != null) {
				if (!this.allowBeanDefinitionOverriding) {
					throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
							"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
							"': There is already [" + oldBeanDefinition + "] bound.");
				}
				else {
					if (this.logger.isInfoEnabled()) {
						this.logger.info("Overriding bean definition for bean '" + beanName +
								"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
					}
				}
			}
			else {
				this.beanDefinitionNames.add(beanName);
				this.frozenBeanDefinitionNames = null;
			}
			this.beanDefinitionMap.put(beanName, beanDefinition);

最後就通過beanDefinitionMap來操作beanDefinition了。

2.5 IoC容器的啓動

FileSystemXmlApplicationContext的構造函數如下,啓動了IoC容器。

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
			throws BeansException {
		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}

refresh()方法在FileSystemXmlApplicationContext的父類AbstractApplicationContext中實現。在refresh()方法中,最終調用了refreshBeanFactory()方法。

 

完整IoC容器啓動分析(以FileSystemXmlApplicationContext爲例)

1.  FileSystemXmlApplicationContext構造方法調用refresh()方法,這裏是載入beanDefinition的入口

2. refresh()方法在FileSystemXmlApplicationContext的父類

AbstractApplicationContext中的實現,調用refreshBeanFactory()方法;

3. refreshBeanFactory()方法在AbstractApplicationContext的子類

AbstractRefreshableApplicationContext中實現,

調用了loadBeanDefinition()方法,啓動對beanDefinition的載入;

4. loadBeanDefinition()在AbstractXmlApplicationContext中實現,調用了XmlBeanDefinitionReader的loadBeanDefinitions()方法。

5. XmlBeanDefinitionReader的loadBeanDefinitions()實現了對於承載beanDefnition定義的xml文件的讀入,以I/O的方式。

6. 讀入後,對beanDefinition進行解析。Bean解析採用SAX工具,先按照XML文件格式解析,再按照spring bean也有的定義解析,在BeanDefinitionParserDelegate.parseBeanDefinitionElement()實現。

7. 最後對beanDefinition信息進行註冊。就是將每個beanDefinition以key  =beanName,value = beanDefinition放入一個hashMap中,在DefaultListenableFactoty.RegisterBeanDefinition()中實現。

 

經過IoC容器的初始化後,IoC容器持有beanDefintion,爲依賴注入bean即調用getBean()方法奠定了基礎。



發佈了125 篇原創文章 · 獲贊 50 · 訪問量 112萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章