07.Spring Bean 解析 - BeanDefinitionDocumentReader
基本概念
BeanDefinitionDocumentReader ,該類的作用有兩個,完成 BeanDefinition 的解析和註冊 。
-
解析:其實是解析 Ddocument 的內容並將其添加到 BeanDefinition 實例的過程。
-
註冊:就是將 BeanDefinition 添加進 BeanDefinitionHolder 的過程,這樣做的目的是保存它的信息。
下面來看它的接口定義,該接口只定義了一個方法負責完成解析和註冊的工作:
public interface BeanDefinitionDocumentReader {
void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)throws BeanDefinitionStoreException;
}
再來看它的繼承關系,默認只有一個實現類:
源碼分析
接下來來看 DefaultBeanDefinitionDocumentReader 類中的 registerBeanDefinitions 方法。
首先來看該方法的入參:
-
Document:代指 Spring 的配置文件信息,通過 BeanDefinitionReader 解析 Resrouce 實例得到。
-
XmlReaderContext :主要包含了 BeanDefinitionReader 和 Resrouce 。
再來看它的具體流程:
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
// 日誌輸出...
// 取得根元素,即 XML 文件中的 <beans> 標簽
Element root = doc.getDocumentElement();
// 關鍵 -> 繼續 Document 的解析
doRegisterBeanDefinitions(root);
}
關於 doRegisterBeanDefinitions,該方法的主要作用有:
-
創建 BeanDefinitionParserDelegate 對象,用於將 Document 的內容轉成 BeanDefinition 實例,也就是上面提到的解析過程,BeanDefinitionDocumentReader 本身不具備該功能而是交給了該類來完成。
-
取得 beans 標簽中 profile 的屬性內容,該標簽主要用於環境的切換。例如開發過程中,一般存在測試環境和正式環境,兩者之間可能存在不同的數據源。若想要實現環境的快速切換,就可以利用 profile 來配置。具體實現這裏暫不探究。
protected void doRegisterBeanDefinitions(Element root) {
// 創建 delegate 對象
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
// 驗證 XML 文件的命名空間,即判斷是否含有 xmlns="http://www.springframework.org/schema/beans"
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)) {
return;
}
}
}
// 空方法
preProcessXml(root);
// 關鍵 -> 開始解析 Bean 定義
parseBeanDefinitions(root, this.delegate);
// 空方法
postProcessXml(root);
this.delegate = parent;
}
下面開始通過遍歷取得 beans 元素的所有子節點。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 驗證 XML 文件的命名空間
if (delegate.isDefaultNamespace(root)) {
// 取得 <beans> 的所有子節點
NodeList nl = root.getChildNodes();
// 遍歷 <beans> 的子節點
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 判斷節點是不是 Element 類型
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// 關鍵 -> 解析 <beans> 子節點的內容
parseDefaultElement(ele, delegate);
}else {
// 解析自定義元素,暫不探究
delegate.parseCustomElement(ele);
}
}
}
}else {
delegate.parseCustomElement(root);
}
}
在拿到了子節點後,開始解析 beans 標簽的子節點,常見的標簽有 import,alias,bean,beans 等。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 處理 <import> 標簽,將資源進行合並再統一解析
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// 處理 <alias> 標簽
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// 關鍵-> 處理 <bean> 標簽
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
// 處理 <beans> 標簽,回到開始解析 document 的地方
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
這裏關鍵來探究下 bean 的解析過程:
-
上面提到 bean 標簽的具體解析工作交給 BeanDefinitionParserDelegate 類來完成。
-
在完成解析取得 BeanDefinition(被添加進了 BeanDefinitionHolder ) 對象之後利用 BeanDefinitionRegistry 完成註冊過程。
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 關鍵 -> ①交給委托類 delegate 來完成解析過程 ,並返回 BeanDefinitionHolder 對象
// 該對象存儲了 BeanDefinition 的基本信息
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
// 交給委托類 delegate 來完成修飾過程,這裏暫不探究
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 關鍵 -> ②註冊最後的裝飾實例
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}catch (BeanDefinitionStoreException ex) {
//錯誤輸出...
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
觀察上述代碼,發現 DefaultBeanDefinitionDocumentReader 的主要職責是解析 Document ,取得配置文件(這裏指 xml )中定義的標簽內容;而解析標簽的過程交給 BeanDefinitionParserDelegate 類完成;註冊過程交給了 BeanDefinitionRegistry 接口來完成。
1.BeanDefinition 解析
在 DefaultBeanDefinitionDocumentReader 關於 bean 標簽的解析方法如下:
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
- 1
下面來看下 BeanDefinitionParserDelegate 的 parseBeanDefinitionElement 方法。
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
// 取得 <bean> 標簽的 id 屬性
String id = ele.getAttribute(ID_ATTRIBUTE);
// 取得 <bean> 標簽的 name 屬性
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// 創建 List 用於存方法 alsas(別名)集合
List<String> aliases = new ArrayList<String>();
if (StringUtils.hasLength(nameAttr)) {
// 將 nameArr 按照(,)或(;)分割成數組
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// 添加進行別名集合
aliases.addAll(Arrays.asList(nameArr));
}
// 判斷 id 是否為空,若為空則取別名集合的第一個元素當作 id ,並將其從別名集合當中移除
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
// 日誌輸出...
}
// 檢查 id(標識)和 alias(名別)是否唯一
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
// 關鍵 -> 解析 <bean> 標簽的 class 屬性 以及相關特性標簽,並將其添加進 beanDefinition 返回
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
// 判斷是否存在 beanName(id)
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
}else {
// 由 Spring 自動生成 beanName(id)
beanName = this.readerContext.generateBeanName(beanDefinition);
// 取得 bean 的 完整類名可用
String beanClassName = beanDefinition.getBeanClassName();
// 將 beanName 添加進 alais 集合
if (beanClassName != null &&
beanName.startsWith(beanClassName) &&
beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
// 日誌輸出...
}catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
// 將 aliases 集合數組化
String[] aliasesArray = StringUtils.toStringArray(aliases);
// 關鍵 -> 返回一個 BeanDefinitionHolder 實例,用於存儲信息
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
再來看解析 bean 標簽的 class 屬性以及相關特性標簽,並將其添加進 beanDefinition 返回的具體過程:
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) {
// 入棧操作,往 parseState 中添加一個 新建的 BeanEntry
this.parseState.push(new BeanEntry(beanName));
// 取得 <bean> 標簽的 class 屬性
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
// 取得 <bean> 標簽的 parent 屬性
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
// 根據 class,parent 的屬性值創建一個 BeanDefinition
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
// 取得 <bean> 標簽的其他特性屬性,並添加進 BeanDefinition 。如:
// scope、
// lazy-init
// autowire
// primary、autowire-candidate
// depends-on、dependency-check
// init-method、destroy-method
// factory-method、factory-bean
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
// 解析 <mate> 標簽
parseMetaElements(ele, bd);
// 解析 <lookup-method> 標簽
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
// 解析 <replaced-method> 標簽
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
// 解析 <constructor-arg> 標簽
parseConstructorArgElements(ele, bd);
// 解析 <property> 標簽
parsePropertyElements(ele, bd);
// 解析 <qualifier> 標簽
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}catch (ClassNotFoundException ex) {
// 錯誤輸出...
}catch (NoClassDefFoundError err) {
// 錯誤輸出...
}catch (Throwable ex) {
// 錯誤輸出...
}finally {
// 出棧操作
this.parseState.pop();
}
return null;
}
2.Beandefinition 註冊
在 DefaultBeanDefinitionDocumentReader 關於 bean 標簽的註冊方法如下:
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
- 1
下面來看 BeanDefinitionReaderUtils 的 registerBeanDefinition 方法。該方法的主要作用是調用註冊器完成註冊過程。
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// 取得 BeanDefinition 實例的標識
String beanName = definitionHolder.getBeanName();
// 關鍵 -> 調用註冊器實現註冊過程
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 取得 BeanDefinition 實例的所有別名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
// 往註冊器的 aliasMap 添加 alias 的過程
registry.registerAlias(beanName, alias);
}
}
}
07.Spring Bean 解析 - BeanDefinitionDocumentReader