Spring4.3.x 淺析xml配置的解析過程(1)——使用XmlBeanDefinitionReader解析xml配置
概述
Spring預設的BeanFactory是DefaultListableBeanFactory類,spring建立DefaultListableBeanFactory物件後,會把配置資訊轉換成一個一個的BeanDefinition物件,並把這些BeanDefinition物件註冊到DefaultListableBeanFactory物件中,以供bean工廠建立bean例項。BeanDefinition物件儲存的是單個bean的配置資訊,比如依賴類、scope、是否延遲載入等等。
Spring可以通過4種方式配置bean,其一是基於xml的配置,其二種是基於xml+註解的配置,其三是基於java+註解的配置,其四是基於property檔案的配置。前兩種的配置資訊使用XmlBeanDefinitionReader物件來解析;第三種的配置資訊使用AnnotatedBeanDefinitionReader物件來解析;最後一種的配置資訊使用PropertiesBeanDefinitionReader物件來解析。
而這裡要討論的主題是xml配置的解析過程,因此我們的戰場在XmlBeanDefinitionReader,首先看看它的繼承結構,如下圖。
簡單的說XmlBeanDefinitionReader實現了BeanDefinitionReader介面,BeanDefinitionReader的設計用意是載入BeanDefintion物件,下面是它載入BeanDefintion的4個介面方法。
/**
* 從單個指定的資源物件中載入BeanDefintion,並返回載入的BeanDefintion個數
*/
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
/**
* 從多個指定的資源物件中載入BeanDefintion,並返回載入的BeanDefintion個數
*/
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
/**
* 從單個指定的資源地址中載入BeanDefintion,並返回載入的BeanDefintion個數。
* 如果資源載入器是ResourcePatternResolver物件,那麼location引數可以使用萬用字元。
*/
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
/**
* 從多個指定的資源地址中載入BeanDefintion,並返回載入的BeanDefintion個數。
*/
int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
這4個介面在後面會一一談到,這裡還有個問題留給大家,既然是載入BeanDefintion,為什麼不是返回所有載入的BeanDefintion物件,而是返回載入的個數?
在spring中直接使用XmlBeanDefinitionReader的容器有XmlWebApplicationContext、ClassPathXmlApplicationContext、FileSystemXmlApplicationContext。下面就從XmlWebApplicationContext為例來探索xml配置的解析過程。
XmlWebApplicationContext解析配置檔案分為以下2個過程:
1) 建立並初始化XmlBeanDefinitionReader 物件。
2)使用XmlBeanDefinitionReader 物件提供的介面方法來載入BeanDefinition物件。
下面我們通過spring原始碼來探討這個2個過程。
1 建立並初始化XmlBeanDefinitionReader 物件
XmlWebApplicationContext在建立完BeanFacotry的時候會呼叫它的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法來載入BeanDefinition,loadBeanDefinitions方法的程式碼如下。
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 根據給定的bean工廠建立新的XmlBeanDefinitionReader物件
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 使XmlBeanDefinitionReader物件與上下文物件在同一個資源環境中
beanDefinitionReader.setEnvironment(this.getEnvironment());
// 使用上下文物件為XmlBeanDefinitionReader物件的資源載入器
beanDefinitionReader.setResourceLoader(this);
// 設定EntityResolver物件,用於載入XML的xsd或者dtd檔案
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 鉤子方法,允許子類在載入bean definition之前進一步設定XmlBeanDefinitionReader
// ->比如,更改XmlBeanDefinitionReader自己提供的DocumentLoader
// 或者BeanDefinitionDocumentReader等預設物件
initBeanDefinitionReader(beanDefinitionReader);
// 使用BeanDefinitionReader載入所有的BeanDefinition物件,見下面的程式碼
loadBeanDefinitions(beanDefinitionReader);
}
這段程式碼是為使用BeanDefinitionReader物件載入BeanDefinitioin物件做準備工作。BeanDefinitionRegistry物件是BeanDefinition物件的登錄檔,並且它是XmlBeanDefinitionReader物件建立時必須提供的,除此之外,其它的都使用預設或者由使用者提供。在XmlWebApplicationContext容器裡,容器向BeanDefinitionReader提供了3個物件,第一個是資源環境Environment物件,它可用於判斷beans標籤的profile屬性,後面會談到;第二個是資源載入器ResourceLoader物件;最後一個是用於載入XML驗證檔案(dtd或者xsd檔案)的EntityResolver物件。如果這些還不夠,則可以擴充套件XmlWebApplicationContext容器,並重寫initBeanDefinitionReader方法對BeanDefinitionReader做更多的初始化。
2 使用XmlBeanDefinitionReader 物件來載入BeanDefinition
首先,還是先預覽一下XmlBeanDefinitionReader 載入BeanDefinition的流程:
上圖只是大致描述了XmlBeanDefinitionReader物件載入BeanDefinition的過程,還有些重點的旁枝末節沒有展現出來,但這並不影響我們瞭解XmlBeanDefinitionReader解析xml配置的過程。下面我們看看XmlWebApplicationContext如何使用XmlBeanDefinitionReader物件,以及XmlBeanDefinitionReader物件如何按照這個流程執行的。
建立完成BeanDefinitionReader物件後,XmlWebApplicationContext呼叫它的loadBeanDefinitions(XmlBeanDefinitionReader reader)方法來使用BeanDefinitionReader物件。下面是loadBeanDefinitions(XmlBeanDefinitionReader reader)方法的程式碼。
/**
* 使用XmlBeanDefinitionReader載入所有的BeanDefinition物件
**/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
// 獲取容器需要載入的配置檔案的地址
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
// 使用XmlBeanDefinitionReader一個一個的讀取spring配置檔案
reader.loadBeanDefinitions(configLocation);
}
}
}
這部分程式碼是XmlBeanDefinitionReader 使用xml配置檔案地址載入BeanDefinition物件的入口。這裡所呼叫XmlBeanDefinitionReader的loadBeanDefinitions方法繼承自XmlBeanDefinitionReader的父類AbstractBeanDefinitionReader。下面程式碼是loadBeanDefinitions方法在AbstractBeanDefinitionReader類中的實現。
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// 使用資源模式解析器解析配置檔案的路徑並載入資源
try {
// 載入所有與指定location引數匹配的所有資源
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 載入指定的資源中的所有BeanDefinition
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
} catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
} else {
// 直接載入資源且只加載一個資源,預設使用DefaultResourceLoader的getResource方法
Resource resource = resourceLoader.getResource(location);
// 載入指定的resource中的所有BeanDefinition
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
這段程式碼主要做的事情是,使用BeanDefinitionReader物件所持有的ResourceLoader來生成Resource物件。然後呼叫BeanDefinitionReader的loadBeanDefinitions(Resource… resources)或者loadBeanDefinitions(Resource resource)方法來執行載入BeanDefinition的程式碼,其中,前一個方法通過多個資原始檔來載入,後一個方法通過一個資原始檔來載入。
首先我們從處理多個Resource物件的loadBeanDefinitions(Resource… resources)方法開始,這個方法已經在AbstractBeanDefinitionReader中有實現,並且XmlBeanDefinitionReader直接繼承了它,程式碼如下。
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
// 一個一個資原始檔的載入BeanDefinition
// 此介面方法在AbstractBeanDefinitionReader沒有實現
// XmlWebApplicationContext使用XmlBeanDefinitionReader
// ->則呼叫XmlBeanDefinitionReader的實現
counter += loadBeanDefinitions(resource);
}
return counter;
}
這部分程式碼的所做的事情是遍歷傳入的資源Resource物件,並呼叫loadBeanDefinitions(Resource resource)方法載入每一個資源。這個方法在AbstractBeanDefinitionReader並沒有實現,下面是此方法在XmlBeanDefinitionReader類中的實現程式碼。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
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());
}
// 根據指定的XML檔案載入BeanDefinition
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();
}
}
}
loadBeanDefinitions(EncodedResource encodedResource)方法主要做的事情是從Resource物件中獲取xml檔案輸入流,並用它來建立InputSource物件。然後呼叫XmlBeanDefinitionReader的doLoadBeanDefinitions(InputSource inputSource, Resource resource)方法,程式碼如下。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 載入Document物件
Document doc = doLoadDocument(inputSource, resource);
// 註冊BeanDefinition
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);
}
}
/**
* 獲取Document物件
**/
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
// 使用DocumentLoader來載入Document物件
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware());
}
/**
* 獲得XML驗證模式,預設使用xsd
*/
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
return VALIDATION_XSD;
}
這部分程式碼所做的事情首先是獲取xml文件的驗證模式,spring使用的xsd模式;然後呼叫DocumentLoader的loadDocument來讀取InputSource物件中的XML內容並建立Document物件,XmlBeanDefinitionReader預設的DocumentLoader為DefaultDocumentLoader;最後呼叫registerBeanDefinitions(Document doc, Resource resource)方法來處理剛建立的Document物件,下面是XmlBeanDefinitionReader類中registerBeanDefinitions方法的程式碼。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 建立BeanDefinitionDocumentReader物件
// 預設為DefaultBeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// documentReader需要持有當前的環境物件
documentReader.setEnvironment(this.getEnvironment());
int countBefore = getRegistry().getBeanDefinitionCount();
//首先建立XmlReaderContext物件
// 通過BeanDefinitionDocumentReader註冊BeanDefinition
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
從這部分程式碼可以看出,XmlBeanDefinitionReader使用BeanDefinitionDocumentReader 物件來載入Document物件中的配置資訊。
這部分程式碼主要做的事情有3步。第一步是建立BeanDefinitionDocumentReader物件,預設是DefaultBeanDefinitionDocumentReader;第二步是建立呼叫它的registerBeanDefinitions方法所需要的XmlReaderContext上下文物件,XmlReaderContext物件持有當前要讀取的資源、xml名稱空間處理;第三步是呼叫documentReader的registerBeanDefinitions(Document doc, XmlReaderContext readerContext)處理Document物件。下面分別探討這三步的程式碼。
第一步建立BeanDefinitionDocumentReader物件。
/**
* 建立BeanDefinitionDocumentReader物件
**/
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}
第二步:建立XmlReaderContext上下文物件。
/**
* 建立XmlReaderContext物件
**/
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
/**
* 建立NamespaceHandlerResolver物件
**/
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
/**
* 建立預設的名稱空間處理器容器
**/
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}
第三步執行DefaultBeanDefinitionDocumentReader的registerBeanDefinitions(Document doc, XmlReaderContext readerContext)方法。
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
// 獲得root節點
Element root = doc.getDocumentElement();
// 註冊root節點中的所有BeanDefinition
doRegisterBeanDefinitions(root);
}
這一步是處理Document物件的重點也是入口。XML檔案中只允許有一個根節點,上面的程式碼所做的事情就是儲存XmlReaderContext 物件並提取根節點,然後呼叫DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions(Element root)方法,這個方法的程式碼如下。
protected void doRegisterBeanDefinitions(Element root) {
// 在這個方法中,遞迴所有巢狀<beans>元素。
// 為了正確地延用並儲存<beans>元素的default-*屬性
// ->需要把父節點<beans>的delegate記錄下來,也許這個delegate可能為null。
BeanDefinitionParserDelegate parent = this.delegate;
// 為當前的<beans>節點建立delgate物件
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
// 檢查beans標籤上的profile屬性
// 宣告:public static final String PROFILE_ATTRIBUTE = "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);
// 解析完巢狀的<beans>標籤後,還原父節點的delegate
this.delegate = parent;
}
這段程式碼主要也分成3步,第一步建立BeanDefinitionParserDelegate物件,這個物件的作用是代理DefaultBeanDefinitionDocumentReader解析BeanDefinition物件;第二步檢查bean標籤的profile屬性值是否與環境的匹配,如果不匹配則不處理而直接返回;第三步執行parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法。下面分別探討第一步和第三步。
(1) 建立BeanDefinitionParserDelegate物件,程式碼如下
protected BeanDefinitionParserDelegate createDelegate(
XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) {
// 建立BeanDefinitionParserDelegate
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
// 初始化delegate的Defaults
// 如果當前<beans>節點的屬性值等於預設值,則使用父節點<beans>對應的屬性值。
delegate.initDefaults(root, parentDelegate);
return delegate;
}
(2) 執行DefaultBeanDefinitionDocumentReader的parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法,程式碼如下
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 檢查root節點的名稱空間是否為預設名稱空間
// spring配置檔案中預設的名稱空間為"http://www.springframework.org/schema/beans"
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
// 遍歷root節點下的所有子節點
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);
}
}
這段程式碼主要是區分節點的名稱空間,根據不同名稱空間,呼叫相應的方法。如果節點在預設名稱空間(http://www.springframework.org/schema/beans),則呼叫DefaultBeanDefinitionDocumentReader的parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)方法,否則呼叫BeanDefinitionParserDelegate 的parseCustomElement(Element ele)方法。
(1)處理自定義名稱空間下的節點。執行BeanDefinitionParserDelegate的 parseCustomElement(Element ele) 方法,程式碼如下
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
這部分程式碼主要是獲取自定義名稱空間的處理器,然後執行處理器的parse方法來建立一個BeanDefinition物件。
(2)處理預設名稱空間下的節點。 執行DefaultBeanDefinitionDocumentReader的parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)方法,程式碼如下。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
// 處理import節點元素
// 這裡同樣是獲取配置檔案並把註冊BeanDefinition物件
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
// 處理alias節點元素
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
// 處理bean節點元素
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// 處理beans節點元素,遞迴呼叫doRegisterBeanDefinitions,詳見4.3
doRegisterBeanDefinitions(ele);
}
}
這段程式碼是處理import、beans、alias、bean標籤的入口方法。
- import標籤是引入其它spring配置檔案;
- beans標籤是對bean進行分類配置,比如用一個beans來管理測試環境的bean,用另一個beans來管理生產環境的bean;
- alias標籤是為一個已定義了的bean取別名,它的name屬性值是bean的id,alias屬性值是要取的別名,多個別名用英文逗號、分號或者空格隔開;
- bean標籤的資訊就是spring要例項化的物件。
不管是什麼標籤,只有利用bean標籤才會生成BeanDefinition物件,下面重點探討的bean標籤處理。 執行DefaultBeanDefinitionDocumentReader的processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)方法,程式碼如下。
/**
* 解析bean節點,並註冊BeanDefinition物件
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 建立BeanDefinitionHolder
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 裝飾BeanDefinition
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 註冊已經建立好的BeanDefintion
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// 傳送BeanDefinition註冊事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
這段程式碼分成三步。第一步,根據傳入的Element物件(bean標籤的)呼叫代理物件的parseBeanDefinitionElement(Element ele)方法建立BeanDefinitionHolder 物件,這個物件持有建立好的BeanDefinition物件、bean的id和bean的別名。
第二步,呼叫代理物件的decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder)來對BeanDefinition物件再加工,主要是解析bean標籤中自定義屬性和自定義標籤。
第三步,呼叫工具類BeanDefinitionReaderUtils的registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)方法,這個方法用於註冊建立好的BeanDefinition。
前面兩步主要是完成BeanDefintion的建立和自定義,在這裡不做更深入的探討,下面我們看看第三步,註冊BeanDefinition物件。 執行BeanDefinitionReaderUtils的registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)方法,把BeanDefinition物件註冊到BeanDefinitionRegistry 物件中
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName();
// 把BeanDefinition物件註冊到BeanDefinitionRegistry 物件中
// 我們通過DefaultListableBeanFactory為例,介紹BeanDefinition的註冊
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 把bean id與別名關聯起來
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String aliase : aliases) {
registry.registerAlias(beanName, aliase);
}
}
}
這部分程式碼的作用是呼叫BeanDefinitionRegistry 的registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法來註冊BeanDefinition,然後呼叫它的registerAlias(String name, String alias)來使bean的id和別名關聯起來。
DefaultListableBeanFactory實現了BeanDefinitionRegistry 介面的registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法,程式碼如下。
//---------------------------------------------------------------------
// Implementation of BeanDefinitionRegistry interface
//---------------------------------------------------------------------
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name 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(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
BeanDefinition oldBeanDefinition;
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
} else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled()) {
this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
oldBeanDefinition + "] with [" + beanDefinition + "]");
}
} else if (!beanDefinition.equals(oldBeanDefinition)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
// 覆蓋原來的BeanDefinition
this.beanDefinitionMap.put(beanName, beanDefinition);
} else {
if (hasBeanCreationStarted()) {
// BeanDefintion已經註冊完成,而其他bean正在建立
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
} else {
// 註冊BeanDefintion還未完成
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (oldBeanDefinition != null || containsSingleton(beanName)) {
// 重置BeanDefintiion
resetBeanDefinition(beanName);
}
}
這段程式碼的主要作用是DefaultListableBeanFactory把建立完成的BeanDefinition儲存到Map物件beanDefinitionMap中,同時還要把以前已經建立好的bean(如果存在)銷燬掉。
總結
1. XmlBeanDefinitionReader的真正身份
XmlBeanDefinitionReader並不是xml配置的真正解析者,它只是相當於一個指揮官。當它收到一條需要載入BeanDefinition物件的任務後,它只會協調手下去完成相應的工作,它的手下有:
ResourceLoader,它把指定的配置檔案地址封裝成Resource物件。
DocumentLoader,它把Resource物件中的XML檔案內容轉換為Document物件。預設使用DocumentLoader的實現類DefaultDocumentLoader來載入Document物件。
BeanDefinitionDocumentReader,它把Document物件中包含的配置資訊轉換成BeanDefinition物件並把它註冊到BeanDefintionRegistry物件中。預設使用DefaultBeanDefinitionDocumentReader來操作Document物件。在DefaultBeanDefinitionDocumentReader的實現中,它的責任是遍歷xml根節點下的子節點,並把處理bean標籤和自定義名稱空間的標籤(比如aop:,context:,p:等)的細節委託給BeanDefinitionParserDelegate物件,BeanDefinitionParserDelegate才是真正解析配置檔案的地方。
NamespaceHandlerResolver,用於獲取非預設名稱空間的處理器,預設是DefaultNamespaceHandlerResolver物件。它雖然由XmlBeanDefinitionReader提供,但真正的使用者是BeanDefinitionParserDelegate類。
2. 更改XmlBeanDefinitionReader的預設物件
比如需要更改DocumentLoader、NamespaceHandlerResolver或者BeanDefinitionDocumentReader,甚至是ResourceLoader。
繼承XmlWebApplicationContext,並重寫initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader),這個方法在XmlWebApplicationContext只是一個空實現,特意留給子類在使用XmlBeanDefinitionReader物件載入BeanDefinition之前對這個物件進行定製的。
遺留問題
這一篇文章只是大致的過了一下XmlBeanDefinitionReader如何根據指定的配置檔案地址來載入並註冊BeanDefintion物件,至於以下細節並沒有過多的描述。
ResourceLoader如何根據指定的location生成Resource物件。
DocumentLoader如何根據xml輸入流生成為Document物件。
Spring如何解析bean標籤的屬性以及子節點。
Spring如何解析非預設名稱空間的配置。
Spring如何解析註解配置。