《Spring原始碼深度解析》讀書筆記
預設標籤的解析是在parseDefaultElement函式中進行的,
// DefaultBeanDefinitionDocumentReader.java
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, "import")) {
// 對import標籤的處理
this.importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, "alias")) {
// 對alias標籤的處理
this.processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, "bean")) {
// 對bean標籤的處理
this.processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, "beans")) {
// 對beans標籤的處理
this.doRegisterBeanDefinitions(ele);
}
}
bean標籤的解析及註冊
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException var5) {
this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5);
}
this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
- 通過BeanDefinitionParserDelegate類的parseBeanDefinitionElement進行元素解析,返回BeanDefinitionHolder,返回的例項包含了配置檔案中配置的各種屬性了,比如class、name、id、alias之類的屬性
- 當第一步返回的例項不為空時,若存在預設標籤的子節點下再有自定義屬性,還需要再次對自定義標籤進行解析
- 對第一步返回的例項進行註冊,註冊操作委託給了BeanDefinitionReaderUtils
- 發出響應事件,通過相關的監聽器,這個bean已經載入完成了
解析BeanDefinition
// BeanDefinitionParserDelegate.java
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return this.parseBeanDefinitionElement(ele, (BeanDefinition)null);
}
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
// 解析id屬性
String id = ele.getAttribute("id");
// 解析name屬性
String nameAttr = ele.getAttribute("name");
// 分割name屬性
List<String> aliases = new ArrayList();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, ",; ");
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
if (!StringUtils.hasText(id) && !aliases.isEmpty()) {
beanName = (String)aliases.remove(0);
if (this.logger.isDebugEnabled()) {
this.logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases");
}
}
if (containingBean == null) {
this.checkNameUniqueness(beanName, aliases, ele);
}
//
AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
// 如果不存在beanName那麼根據Spring中提供的命名規則為當前bean生成對應的beanName
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
} else {
beanName = this.readerContext.generateBeanName(beanDefinition);
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Neither XML 'id' nor 'name' specified - using generated bean name [" + beanName + "]");
}
} catch (Exception var9) {
this.error(var9.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
} else {
return null;
}
}
- 提取元素中的id以及name屬性
- 進一步解析其他所有屬性並統一封裝至GenericBeanDefinition型別的例項中
- 如果檢測到bean沒有指定beanName,那麼使用預設規則為此Bean生成beanName
- 將獲取到的資訊封裝到BeanDefinitionHolder例項中
BeanDefinition
BeanDefinition是一個介面,在Spring中存在三種實現:RootBeanDefinition、ChildBeanDefinition以及GenericBeanDefinition。三種實現均繼承了AbstractBeanDefinition,其中BeanDefinition是配置檔案<bean>
元素標籤在容器中的內部表示形式。
Spring通過BeanDefinition將配置檔案中的<bean>
配置資訊轉換為容器的內部表示,並將這些BeanDefinition註冊到BeanDefinitionRegistry中。Spring容器的BeanDefinitionRegistry就像是Spring配置資訊的記憶體資料庫,主要是以map形式儲存,後續操作直接從BeanDefinitionRegistry中讀取配置資訊。
解析預設標籤中的自定義標籤元素
接下來我們看bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
這一行程式碼,從方法名上看:如果需要的話就對beanDefinition進行裝飾。
當Spring中的bean使用的是預設的標籤配置,但是其中的子元素卻使用了自定義的配置時,這句程式碼便會起作用了。
<bean id="test" class="test.MyClass">
<mybean:user username="aaa"/>
</bean>
這個自定義型別並不是以Bean的形式出現的。
具體的方法就不貼出來了,主要的條理是:首先獲取屬性或元素的名稱空間,以此來判斷該元素或者屬性是否適用於自定義標籤的解析條件,找出自定義型別所對應的NamespaceHandler並進行進一步解析。
總結一下decorateBeanDefinitionIfRequired方法的作用,在decorateBeanDefinitionIfRequired中我們可以看到對於程式預設的標籤的處理其實是直接略過的,因為預設的標籤到這裡已經被處理完了,這裡只對自定義的標籤或者說對bean的自定義屬性感興趣。在方法中實現了尋找自定義標籤並根據自定義標籤尋找名稱空間處理,並進行進一步的解析。
註冊解析的BeanDefinition
對配置檔案的解析和裝飾都完成了,唯一剩下的就是註冊了。
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
// BeanDefinitionReaderUtils.java
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
// 使用beanName做唯一標識註冊
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 註冊所有的別名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
String[] var4 = aliases;
int var5 = aliases.length;
for(int var6 = 0; var6 < var5; ++var6) {
String alias = var4[var6];
registry.registerAlias(beanName, alias);
}
}
}
解析的beanDefinition都會被註冊到BeanDefinitionRegistry中,而對於beanDefinition的註冊分成了兩部分:通過beanName的註冊以及通過別名的註冊
通過beanName註冊beanDefinition
對於beanDefinition的註冊,就是講beanDefinition直接放入map中就好了,使用beanName作為key。只不過除此之外,還做了點別的事情。主要進行了幾個步驟:
1. 對AbstractBeanDefinition的校驗。在解析XML檔案的時候我們tguo校驗,但是此校驗非彼校驗,之前的校驗是針對於XML格式的校驗,而此時的校驗是針對於AbstractBeanDefinition的methodOverrides屬性的。methodOverrides屬性中記錄了look-up,replaced-method等子元素
2. 對beanName已經註冊的情況的處理。如果設定了不允許bean的覆蓋,則需要丟擲異常,否則直接覆蓋
3. 加入map快取
4. 清楚解析之前留下的對應beanName的快取
通過別名註冊beanDefinition
- alias與beanName相同情況處理。若alias與beanName名稱相同則不需要處理並刪除掉原有alias
- alias覆蓋處理。若aliasName已經使用並已經指向另一個beanName則需要使用者的設定進行處理
- alias迴圈檢查。當A->B存在時,若再次出現A->C->B時候則會丟擲異常
- 註冊alias
通過監聽器解析及註冊完成
this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
這裡的實現只為擴充套件,當程式開發人員需要對註冊BeanDefinition事件進行監聽時可以通過註冊監聽器的方式並將處理邏輯寫入監聽器中。
alias標籤的解析
在對bean進行定義時,除了使用id屬性來指定名稱外,為了提供多個名稱,可以使用alias標籤來指定。而所有的這些名稱都指向同一個bean。
如配置檔案中定義了一個JavaBean:
<bean id="testBean" calss="com.test" />
要給這個JavaBean增加別名,以方便不同物件來呼叫。我們就可以直接使用bean標籤中的name屬性:
<bean id="testBean" name="testBean1, testBean2" class="com.test" />
同樣Spring還有另外一種宣告別名的方式:
<bean id="testBean" calss="com.test" />
<alias name="testBean" alias="testBean1,testBean2" />
import標籤的解析
對於Spring配置檔案的編寫,分模組是常用的技巧。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="customerContext.xml" />
<import resource="systemContext.xml" />
</beans>
使用import的方式匯入模組配置檔案,以後若有心模組的加入,那就可以簡單修改這個檔案,大大簡化了配置後期維護的複雜度。
在解析import標籤時,Spring進行解析的步驟大致如下:
1. 獲取resource屬性所表示的路徑
2. 解析路徑中的系統屬性,如${user.dir}
3. 判定location是絕對路徑還是相對路徑
4. 如果是絕對路徑則遞迴呼叫bean的解析過程,進行另一次的解析
5. 如果是相對路徑則計算出絕對路徑並進行解析
6. 通知監聽器,解析完成
嵌入式beans標籤的解析
對於嵌入式beans標籤來講,與單獨配置檔案並沒有太大差別,無非是遞迴呼叫bean的解析過程