【死磕 Spring】----- IOC 之 註冊 BeanDefinition
獲取 Document 物件後,會根據該物件和 Resource 資源物件呼叫 registerBeanDefinitions()
方法,開始註冊 BeanDefinitions 之旅。如下:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
首先呼叫 createBeanDefinitionDocumentReader()
方法例項化 BeanDefinitionDocumentReader 物件,然後獲取統計前 BeanDefinition 的個數,最後呼叫 registerBeanDefinitions()
例項化 BeanDefinitionDocumentReader 物件方法如下:
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}
註冊 BeanDefinition 的方法 registerBeanDefinitions()
是在介面 BeanDefinitionDocumentReader 中定義,如下:
void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
throws BeanDefinitionStoreException;
從給定的 Document 物件中解析定義的 BeanDefinition 並將他們註冊到登錄檔中。方法接收兩個引數,待解析的 Document 物件,以及解析器的當前上下文,包括目標註冊表和被解析的資源。其中 readerContext 是根據 Resource 來建立的,如下:
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
DefaultBeanDefinitionDocumentReader 對該方法提供了實現:
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
呼叫 doRegisterBeanDefinitions()
開啟註冊 BeanDefinition 之旅。
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
// 處理 profile
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
// 解析前處理
preProcessXml(root);
// 解析
parseBeanDefinitions(root, this.delegate);
// 解析後處理
postProcessXml(root);
this.delegate = parent;
}
程式首先處理 profile屬性,profile主要用於我們切換環境,比如切換開發、測試、生產環境,非常方便。然後呼叫 parseBeanDefinitions()
進行解析動作,不過在該方法之前之後分別呼叫 preProcessXml()
和 postProcessXml()
方法來進行前、後處理,目前這兩個方法都是空實現,交由子類來實現。
protected void preProcessXml(Element root) {
}
protected void postProcessXml(Element root) {
}
parseBeanDefinitions()
定義如下:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
最終解析動作落地在兩個方法處:parseDefaultElement(ele, delegate)
和 delegate.parseCustomElement(root)
。我們知道在 Spring 有兩種 Bean 宣告方式:
- 配置檔案式宣告:
<bean id="studentService" class="org.springframework.core.StudentService"/>
- 自定義註解方式:
<tx:annotation-driven>
兩種方式的讀取和解析都存在較大的差異,所以採用不同的解析方法,如果根節點或者子節點採用預設名稱空間的話,則呼叫 parseDefaultElement()
進行解析,否則呼叫 delegate.parseCustomElement()
方法進行自定義解析。
至此,doLoadBeanDefinitions()
中做的三件事情已經全部分析完畢,下面將對 Bean 的解析過程做詳細分析說明。