Spring源碼學習之IOC(1)

[align=center][size=x-large][b]IOC初始化[/b][/size][/align]

最近再看Spring技術內幕。發現裏面寫的挺不錯,就是個人覺得有點亂。這裏按照程序執行順序重新整理一遍,方便理解。
再次聲明:僅是個人意見!!!

[align=center][img]http://dl.iteye.com/upload/attachment/459679/f98c8d17-1fbb-3ac6-97e0-7d14ff98a7fc.png[/img]
[size=small][b]Figure1[/b][/size]

[img]http://dl.iteye.com/upload/attachment/459681/ef5c6064-a31b-3abe-a9cf-ef7026cdcb37.png[/img]
[size=small][b]Figure2[/b][/size]

[img]http://dl.iteye.com/upload/attachment/459689/0d582314-74f8-3e9e-8cb7-338ea60f01e2.png[/img]
[size=small][b]Figure3[/b][/size]

[img]http://dl.iteye.com/upload/attachment/459685/ac157b5f-dce6-39c0-9740-b938aecfdc2c.png[/img]
[size=small][b]Figure4[/b][/size]

[img]http://dl.iteye.com/upload/attachment/459677/ef2fe1d9-7fcb-31de-8aaf-8c3671ed7976.png[/img][/align]
[size=small][b]Figure5[/b][/size]

以FileSystemXmlApplicationContext爲例,初始化的三個部分:
1. 配置文件資源(Resource interface)的定位
2. 配置文件資源的載入(載入Document對象並且解析成BeanDefinition的格式)
3. BeanDefinition在IOC容器中的註冊(這一部分包括將BeanDefinition註冊入容器)
代碼1:

ClassPathResource res = new ClassPathResource(“beans.xml”);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
Reader.loadBeanDefinitions(res);


上面代碼是一個簡單的初始化一個IOC容器。其中DefaultListableBeanFactory是一個BeanFactory的實現類。其只是個IOC容器,而ApplicationContext是對上述進行了一個封裝。
首先BeanDefinition是Xml中Bean標籤的抽象,FileSystemXmlApplicationContext底層還是使用的DefaultListableBeanFactory。由Figure3可以看出AbstractApplicationContext實現了ResourceLoader,所以資源的定位這一部分是由AbstractApplicationContext完成的,而FileSystemXmlApplicationContext重寫了getResourceByPath(String path)方法,所以具體定位是由FileSystemXmlApplicationContext完成的
第二部分這裏使用了兩個類DefaultBeanDefinitionDocumentReader和BeanDefinitionParserDelegate,前者是讀取BeanDefinition的信息,後者是解析BeanDefinition的信息完成對Xml中Bean標籤的抽象
由figure5可以看出DefaultListableBeanFactory繼承了BeanDefinitionRegistry。所以第三部分BeanDefinition是由DefaultListableBeanFactory完成的。

[size=large][b]1.啓動初始化[/b][/size]
在FileSystemXmlApplicationContext的構造方法FileSystemXmlApplicationContext(String[] configLocationions,boolean refresh,Application parent)中調用了方法refresh(),該方法定義了整個初始化IOC的流程,也是IOC初始化的入口.具體實現在其父類AbstractApplicationContext中實現。改方法中有句代碼如ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();這句主要就是刷新(刷新的過程其實就是銷燬所有的bean再關閉BeanFactory然後再新建)當前的bean facotry也就是IOC容器並且返回,當然如當前沒有IOC那就新建一個返回。在obtainFreshBeanFactory()方法中調用refreshBeanFactory()方法,該方法是由其子類AbstractRefreshableApplicatioContext實現的。改方法中就會判斷是否已經存在了IOC容器。判斷完之後緊接着就是創建一個beanFactory代碼如下:
[b]代碼2[/b]
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}


從這裏可以看出其底層依舊是使用DefaultListableBeanFactory.從這裏主要開始看loadBeanDefinitions這個方法了。這個loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法是個抽象方法具體實現需要在其子類AbstractXmlApplicationContext中查找代碼如下:
[b]代碼3:[/b]

protected void loadBeanDefinitions(DefaultListableBeanFactory
beanFactory) throws BeansException, IOException {
XmlBeanDefinitionReader beanDefinitionReader = new
XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new
ResourceEntityResolver(this));
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}


這段代碼看起來和之前的代碼一有相似之處了。但還有不同之處,由於代碼1中我們直接定義了一個Resource換而言之就是我們手動的定位了Resource。而這裏我們能獲取Resource的唯一途徑就在我們構造FileSystemXmlApplication的時候傳入的configLocatio-ns這個文件路徑數組。所以我們需要使用setResourceLoader(ResourceLoader resourceLoader)這個方法將一個Resource加載器給當前的BeanDefinition的讀取器XmlBeanDefinitionReader,讓它內部能夠自動完成Resource的定位和創建。然後就是將這個XmlBeanDefinitionReader作爲參數傳遞給loadBeanDefinitions的一個重載方法loadBeanDefinitions(XmlBeanDefinitionReader reader)中。在這個loadBeanDefinitions的方法中。從這裏開始將進入配置文件資源Resource定位的部分了。

[size=large][b]2.配置文件資源Resource定位[/b][/size]
上面講到進入重載方法loadBeanDefinitions(XmlBeanDefinitionReader reader)。在這個方法中代碼如下:
[b]代碼4:[/b]

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


從代碼能夠發現,它在這裏不僅會檢測配置文件路徑configLocations還會檢測已經定位好的Resource通過getConfigResources()方法。但是在該文件的源代碼中的getConfigResources()方法中只有個return null的語句。所以if (configResources != null)始終不通過,換言之。這裏只檢測配置文件路徑,這裏可能是爲了後續的拓展。至於getConfigLocations(),改方法繼承自其父類AbstractRefreshableConfigApplicationContext中的。具體實現語句就是判斷configLocations是否爲null,如果不是null返回當前值,是null返回默認值。由於之前構造FileSystemXmlApplicationContext的時候已經將configLoactions傳入進去了。所以這裏直接進入if語句。調用loadBeanDef-initions(String[] locations)方法,該是繼承自XmlBeanDefinitionReader父類AbstractBeanDefinitionReader中的。具體代碼如下:
[b]代碼5:[/b]

public int loadBeanDefinitions(String[] locations) throws
BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be
null");
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}



[b]代碼6:[/b]

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(…);
}
if (resourceLoader instanceof ResourcePatternResolver) {

}
else {
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}

return loadCount;
}
}

這裏首先會判斷ResourceLoader是否爲空。由於之前我們在AbstractXmlApplicationContext裏填入了該值beanDefinitionReader.setResourceLoader(this)。所以繼續執行下面的if。由於我們填入的ResourceLoader並不是ResourcePatternResolver類型所以進入else中。else中執行了Resource resource = resourceLoader.getRes-ource(location).由於之前在AbstractXmlApplicationContext裏調用了beanDefinitionReader.setResourceLoader(this)。而AbstractXmlApplicationContext是DefaultResourceLoader的子類。DefaultResourceLoader又是ResouceLoader的實現類,所以這裏應該調用的是DefaultResourceLoader的getResouce(String location)方法。具體代碼如下:
[b]代碼7:[/b]
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 {
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
return getResourceByPath(location);
}
}
}

由於這裏並不是URL方式表示的配置文件路徑。所以直接跳入cathc語句塊。執行getResourceByPath(location)。由於這個getResource方法是其子類FileSystemXmlApplicationContext調用的,所以調用的是FileSystemXmlApplicationContext重寫的getResourceByPath(location)並且返回一個Resource方法代碼如下:
[b]代碼8:[/b]
	protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}


由於至此Resource的定位部分完成了。

[size=large][b]3.BeanDefinition的加載和解析[/b][/size]
之前已經介紹如何定位Resource.也就是已經將配置文件的IO封裝成Resource並且返回了。讓我們繼續回到代碼6中AbstractBeanDefinitionReader這個類。在獲取了Resource之後繼續執行了loadBeanDefinitions (Resource resource).這裏的resource正是剛纔獲取的。執行的loadBeanDefinitions是BeanDefinitionReader的接口方法。具體的實現實在其實現類AbstractBeanDefinitionReader的子類中。當然loadBeanDefinitions(Resource resource)這個方法也不是直接使用resource去解析。而是將resource進一步封裝成EncodeResource並將其作爲參數傳遞給loadBeanDefinitions(EncodeResource encodeResource)這個重載的方法進行處理.主要代碼如下:
[b]代碼9:[/b]
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(…);
if (logger.isInfoEnabled()) {

}
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(…);
}
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(…);
}finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.set(null);
}
}
}

如代碼所示在這裏會對Resource處理,將IO流從Resource中讀取出來,然後封裝成InputSource並將其傳入doLoadBeanDefinitions。doLoadBeanDefinitions方法依舊是XmlBeanDefinitionReader中的方法.然後doLoadBeanDefinitions將根據inputSource產生出Document對象這個對象就是Xml文件的一個抽象。並且將這個Document傳入XmlBeanDefinitionReader中的registerBeanDefinition方法中去代碼如下
[b]代碼10:[/b]
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);
}

}


配置文件加載完成,現在開始解析部分,將Xml形式解析成BeanDefinition這種形式。首先進入XmlBeanDefinitionReader這個的registerBeandDefinitions這個方法。這個方法內部會新建立一個BeanDefinitionDocumentReader這個對象。該對象是專門用於讀取文檔的一個API。然後再將上面所說的Document對象作爲參數傳遞給BeanDefinitionDocumentReader對象的registerBeanDefinitions(Document doc, XmlReaderContext readerContext)方法中並且調用,該方法中將完成解析的過程代碼如下:
[b]代碼11:[/b]

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { SPI.
BeanDefinitionDocumentReader documentReader =
createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc,
createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}


這裏調用的registerBeanDefinitions這個方法是個接口方法,具體實現在其子類DefaultBeanDefinitionDocumentReader中實現。該子類實現的是僅僅是對Document的讀取功能,具體解析的功能還需要一個類registerBeanDefinitions.所以在registerBeanDefinitions方法中會產生個registerBeanDefinitions類幫助解析。具體如何解析這裏不做說明。自己也能看懂
至此,BeanDefinition的加載和解析也完成了。接下來就是如何註冊了。

[size=large][b]4.BeanDefinition在IOC容器中的註冊[/b][/size]
經過之前的分析已經能夠瞭解到註冊這部分的功能的實現是通過DefaultListableBeanFactory這個Class完成的。而實際上BeanDefinition的註冊這一功能的實現是通過一個名爲beanDefinitionMap的HashMap這樣一個數據結構來維護的。那麼具體是從說明地方開始調用和觸發註冊部分的功能的呢。通過Eclipse發現,如圖所示

[align=center][img]http://dl.iteye.com/upload/attachment/459683/39d775fd-cc75-39ab-b534-024ee3feb700.png[/img]
[b]Figure6[/b][/align]

當在DefaultBeanDefinitionDocumentReader調用processBeanDefinition的時候。當我們解析完BeanDefinition之後通過BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())。這麼一條語句調用的。最後會獲取我們bean的Name和BeanDefinition對象然後作爲參數傳入registerBeanDefinition這個方法完成註冊。註冊代碼如下:
[b]代碼12:[/b]

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
Assert.hasText(beanName, "'beanName' must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(…);
}
}
synchronized (this.beanDefinitionMap) {
Object oldBeanDefinition =
this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!this.allowBeanDefinitionOverriding) {
throw new BeanDefinitionStoreException(…);
}else {
if (this.logger.isInfoEnabled()) {
this.logger.info(…);
}
}
}else {
this.beanDefinitionNames.add(beanName);
this.frozenBeanDefinitionNames = null;
}
this.beanDefinitionMap.put(beanName, beanDefinition);
resetBeanDefinition(beanName);
}
}

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