【死磕 Spring】----- IOC 之 加載 Bean

先看一段熟悉的代碼:

ClassPathResource
resource

new

ClassPathResource
(
"bean.xml"
);
DefaultListableBeanFactory
factory

new

DefaultListableBeanFactory
();
XmlBeanDefinitionReader
reader

new

XmlBeanDefinitionReader
(
factory
);
reader
.
loadBeanDefinitions
(
resource
);
這段代碼是 Spring 中編程式使用 IOC 容器,通過這四段簡單的代碼,我們可以初步判斷 IOC 容器的使用過程。

獲取資源
獲取 BeanFactory
根據新建的 BeanFactory 創建一個BeanDefinitionReader對象,該Reader 對象爲資源的解析器
裝載資源
整個過程就分爲三個步驟:資源定位、裝載、註冊,如下:

資源定位。我們一般用外部資源來描述 Bean 對象,所以在初始化 IOC 容器的第一步就是需要定位這個外部資源。在上一篇博客(【死磕 Spring】----- IOC 之 Spring 統一資源加載策略)已經詳細說明了資源加載的過程。
裝載。裝載就是 BeanDefinition 的載入。BeanDefinitionReader 讀取、解析 Resource 資源,也就是將用戶定義的 Bean 表示成 IOC 容器的內部數據結構:BeanDefinition。在 IOC 容器內部維護着一個 BeanDefinition Map 的數據結構,在配置文件中每一個 <bean> 都對應着一個BeanDefinition對象。
註冊。向IOC容器註冊在第二步解析好的 BeanDefinition,這個過程是通過 BeanDefinitionRegistery 接口來實現的。在 IOC 容器內部其實是將第二個過程解析得到的 BeanDefinition 注入到一個 HashMap 容器中,IOC 容器就是通過這個 HashMap 來維護這些 BeanDefinition 的。在這裏需要注意的一點是這個過程並沒有完成依賴注入,依賴註冊是發生在應用第一次調用 getBean() 向容器索要 Bean 時。當然我們可以通過設置預處理,即對某個 Bean 設置 lazyinit 屬性,那麼這個 Bean 的依賴注入就會在容器初始化的時候完成。
資源定位在前面已經分析了,下面我們直接分析加載,上面提過 reader.loadBeanDefinitions(resource) 纔是加載資源的真正實現,所以我們直接從該方法入手。

public

int
loadBeanDefinitions
(
Resource
resource
)

throws

BeanDefinitionStoreException

{

return
loadBeanDefinitions
(
new

EncodedResource
(
resource
));

}
從指定的 xml 文件加載 Bean Definition,這裏會先對 Resource 資源封裝成 EncodedResource。這裏爲什麼需要將 Resource 封裝成 EncodedResource呢?主要是爲了對 Resource 進行編碼,保證內容讀取的正確性。封裝成 EncodedResource 後,調用 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
<>(
4
);

this
.
resourcesCurrentlyBeingLoaded
.
set
(
currentResources
);

}

// 將當前資源加入記錄中

if

(!
currentResources
.
add
(
encodedResource
))

{

throw

new

BeanDefinitionStoreException
(

"Detected cyclic loading of "

+
encodedResource
+

" - check your import definitions!"
);

}

try

{

// 從 EncodedResource 獲取封裝的 Resource 並從 Resource 中獲取其中的 InputStream

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

}

}

}
首先通過 resourcesCurrentlyBeingLoaded.get() 來獲取已經加載過的資源,然後將 encodedResource 加入其中,如果 resourcesCurrentlyBeingLoaded 中已經存在該資源,則拋出 BeanDefinitionStoreException 異常。完成後從 encodedResource 獲取封裝的 Resource 資源並從 Resource 中獲取相應的 InputStream ,最後將 InputStream 封裝爲 InputSource 調用 doLoadBeanDefinitions()。方法 doLoadBeanDefinitions() 爲從 xml 文件中加載 Bean Definition 的真正邏輯,如下:

protected

int
doLoadBeanDefinitions
(
InputSource
inputSource
,

Resource
resource
)

throws

BeanDefinitionStoreException

{

try

{

// 獲取 Document 實例

Document
doc

doLoadDocument
(
inputSource
,
resource
);

// 根據 Document 實例****註冊 Bean信息

return
registerBeanDefinitions
(
doc
,
resource
);

}

catch

(
BeanDefinitionStoreException
ex
)

{

throw
ex
;

}

catch

(
SAXParseException
ex
)

{

throw

new

XmlBeanDefinitionStoreException
(
resource
.
getDescription
(),

"Line "

+
ex
.
getLineNumber
()

+

" in XML document from "

+
resource
+

" is invalid"
,
ex
);

}

catch

(
SAXException
ex
)

{

throw

new

XmlBeanDefinitionStoreException
(
resource
.
getDescription
(),

"XML document from "

+
resource
+

" is invalid"
,
ex
);

}

catch

(
ParserConfigurationException
ex
)

{

throw

new

BeanDefinitionStoreException
(
resource
.
getDescription
(),

"Parser configuration exception parsing XML from "

+
resource
,
ex
);

}

catch

(
IOException
ex
)

{

throw

new

BeanDefinitionStoreException
(
resource
.
getDescription
(),

"IOException parsing XML document from "

+
resource
,
ex
);

}

catch

(
Throwable
ex
)

{

throw

new

BeanDefinitionStoreException
(
resource
.
getDescription
(),

"Unexpected exception parsing XML document from "

+
resource
,
ex
);

}

}
核心部分就是 try 塊的兩行代碼。

調用 doLoadDocument() 方法,根據 xml 文件獲取 Document 實例。
根據獲取的 Document 實例註冊 Bean 信息。
其實在 doLoadDocument()方法內部還獲取了 xml 文件的驗證模式。如下:

protected

Document
doLoadDocument
(
InputSource
inputSource
,

Resource
resource
)

throws

Exception

{

return

this
.
documentLoader
.
loadDocument
(
inputSource
,
getEntityResolver
(),

this
.
errorHandler
,
getValidationModeForResource
(
resource
),
isNamespaceAware
());

}
調用 getValidationModeForResource() 獲取指定資源(xml)的驗證模式。所以 doLoadBeanDefinitions()主要就是做了三件事情。

調用 getValidationModeForResource() 獲取 xml 文件的驗證模式
調用 loadDocument() 根據 xml 文件獲取相應的 Document 實例。
調用 registerBeanDefinitions() 註冊 Bean 實例。
獲取Java高級進階資料,可加羣828545509

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