Spring源碼解析02:Spring IOC容器之XmlBeanFactory啟動流程分析和源碼解析
一. 前言
Spring容器主要分為兩類BeanFactory和ApplicationContext,後者是基於前者的功能擴展,也就是一個基礎容器和一個高級容器的區別。本篇就以BeanFactory基礎容器接口的默認實現類XmlBeanFactory啟動流程分析來入門Spring源碼的學習。
二. 概念要點
1. 概念定義
-
BeanDefinition:Bean元數據描述,Bean在Spring IOC容器中的抽象,是Spring的一個核心概念 -
DefaultListableBeanFactory : Spring IOC容器的實現,可以作為一個獨立使用的容器, Spring IOC容器的始祖 -
XmlBeanFactory:繼承自DefaultListableBeanFactory,與其不同點在於XmlBeanFactory中使用了自定義的XML讀取器XmlBeanDefinitionReader,實現了個性化的BeanDefinitionReader讀取 -
ApplicationContext: 高級容器定義接口,基於BeanFactory添加了擴展功能,如ResourceLoader、MessageSource、ApplicationEventPublisher等
2. 糟糕!XmlBeanFactory被廢棄了
對Spring有些了解的應該XmlBeanFactory已經過時了。沒錯,本篇要講的XmlBeanFactory在Spring3.1這個很久遠版本就開始過時了。
@deprecated as of Spring 3.1 in favor of {@link DefaultListableBeanFactory}

取而代之的寫法如下
ClassPathResource resource=new ClassPathResource("spring-config.xml");
DefaultListableBeanFactory beanFactory=new DefaultListableBeanFactory();
XmlBeanDefinitionReader beanDefinitionReader=new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.loadBeanDefinitions(resource);
概括DefaultListableBeanFactory + XmlBeanDefinitionReader 取代了 XmlBeanFactory容器的創建和初始化,可以聯想到通過組合的方式靈活度是比XmlBeanFactory高的,針對不同的資源讀取組合的方式只需替換不同的讀取器BeanDefinitionReader就可以了,而XmlBeanFactory中的讀取器XmlBeanDefinitionReader限定其只能讀取XML格式的文件資源,所以至於XmlBeanFactory被廢棄的原因可想而知。
3. XmlBeanFactory?!XML,你會XML解析嗎?
<?xml version="1.0" encoding=" UTF-8" standalone="yes"?><root>
<code>0</code>
<message>調用成功</message>
</root>
附上DOM4J解析的代碼
String xml="<?xml version=\"1.0\" encoding=\" UTF-8\" standalone=\"yes\"?><root>\n" +
"<code>0</code>\n" +
"<message>調用成功</message>\n" +
"</root>";
Document document = DocumentHelper.parseText(xml);
Element root = document.getRootElement();
String code = root.elementText("code");
String message =root.elementText("message");

XML文檔被解析成DOM樹,其中Document是整個DOM的根節點,root為根元素,由根元素一層一層向下解析element元素,容器啟動解析XML流程就是這樣。
三. XmlBeanFactory啟動流程分析
XmlBeanFactory容器啟動就兩行代碼
ClassPathResource resource = new ClassPathResource("spring-config.xml");
XmlBeanFactory beanFactory = new XmlBeanFactory(resource);
怎麼樣?看似簡單就證明了Java封裝的強大,但背後藏了太多。 這裡就送上XmlBeanFactory啟動流程圖,對應的就是上面的兩行代碼。

這是啥?!看得頭暈的看官老爺們別急着給差評啊(瑟瑟發抖中)。這裡精簡下,想快速理解的話直接看精簡版後的,想深入到細節建議看下上面的時序圖。
整個就是bean的加載階段。通過解析XML中的標籤元素生成beanDefinition註冊到beanDefinitionMap中。
四. XmlBeanFactory啟動源碼解析
按照XmlBeanFactory啟動流程的先後順序整理的關鍵性代碼索引列表,其中一級索引為類,二級索引對應其類下的方法。符號 —▷表示接口的實現。建議可以觀察方法索引的參數變化(資源轉換)來分析整個流程,來加深對流程的理解。
-
XmlBeanFactory
-
XmlBeanDefinitionReader
-
DefaultBeanDefinitionDocumentReader —▷ BeanDefinitionDocumentReader
-
registerBeanDefinitions(Document doc, XmlReaderContext readerContext) -
doRegisterBeanDefinitions(Element root) -
parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) -
parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) -
processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)
-
-
BeanDefinitionParserDelegate
-
DefaultListableBeanFactory —▷ BeanDefinitionRegistry
好了,當你心裏對這個流程有個大概的樣子之後,這裡就可以繼續走下去。建議還是很模糊的同學自重,避免走火入魔想對我人身攻擊的。下面就每個方法重要的點一一解析,嚴格按照方法的執行先後順序來說明。
1.1 XmlBeanFactory(Resource resource)
功能概述: XmlBeanFactory的構造方法,整個容器啟動的入口,完成bean工廠的實例化和BeanDefinition加載(解析和註冊)。
/**
* XmlBeanFactory
**/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
// 加載(解析註冊)BeanDefinition
this.reader.loadBeanDefinitions(resource);
}
知識點:
-
Resource:Resource接口是Spring資源訪問策略的抽象,,而具體的資源訪問方式由其實現類完成,如類路徑資源(ClassPathResource)、文件(FileSystemResource)、URL資源(UrlResource)、InputStream資源(InputStreamResource)、Byte數組(ByteArrayResource),根據不同的類型的資源使用對應的訪問策略,明明白白的策略模式。
2.1 loadBeanDefinitions(Resource resource)
功能概述: 上面根據ClassPathResource資源訪問策略拿到了資源Resource,這裡將Resource進行特定的編碼處理,然後將編碼後的Resource轉換成SAX解析XML文件所需要的輸入源InputSource。
/**
* XmlBeanDefinitionReader
**/
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 把從XML文件讀取的Resource資源進行編碼處理
return loadBeanDefinitions(new EncodedResource(resource));
}
/**
* XmlBeanDefinitionReader
**/
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
...
// 完成resource->inputStream->inputSource轉換,使用SAX方式接收輸入源InputSource解析XML
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 執行加載BeanDefinition
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
...
}
知識點:
-
XML解析兩種方式:SAX(Simple API for XML)和DOM(Document Object Model),區別在於SAX按照順序逐行讀取,查找到符合條件即停止,只能讀不能修改,適合解析大型XML;DOM則是一次性讀取到內存建立樹狀結構,佔用內存,不僅能讀還能修改XML。Spring採用的SAX方式來解析XML。
2.2 doLoadBeanDefinitions(InputSource inputSource, Resource resource)
功能概述: 獲取DOM Document對象,XmlBeanDefinitionReader本身沒有對文檔讀取的能力,而是委託給DocumentLoader的實現類DefaultDocumentLoader去讀取輸入源InputResource從而得到Document對象。獲得Document對象後,接下來就是BeanDefinition的解析和註冊。
/**
* XmlBeanDefinitionReader
**/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
...
try {
// inputSource->DOM Document
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
return count;
}
...
}
知識點:
-
Document是DOM的根節點,提供對文檔數據訪問的入口。
2.3 registerBeanDefinitions(Document doc, Resource resource)
功能概述: 解析DOM Document成容器的內部數據接口BeanDefinition並註冊到容器內部。
/**
* XmlBeanDefinitionReader
**/
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 已註冊的BeanDefinition的數量
int countBefore = getRegistry().getBeanDefinitionCount();
// DOM Document->BeanDefinition,註冊BeanDefinition至容器
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 返回此次註冊新增的BeanDefinition數量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
知識點:
-
BeanDefinitionDocumentReader是接口,實際完成對DOM Document解析的是其默認實現類DefaultBeanDefinitionDoucumentReade。 -
createReaderContext(resource)創建XmlReaderContext上下文,包含了XmlBeanDefinitionReader讀取器和NamespaceHandlerResolver 命名空間解析器。
3.1 registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
功能概述: 在此之前一直 是XML加載解析的準備階段,在獲取到Document對象之後就開始真正的解析了。
/**
* DefaultBeanDefinitionDocumentReader
**/
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}
3.2 doRegisterBeanDefinitions(Element root)
功能概述: 解析Element,DefaultBeanDefinitionDoucumentReader同樣不具有解析Element的能力,委託給BeanDefinitionParserDelegate執行。
/**
* DefaultBeanDefinitionDocumentReader
**/
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
...
// 解析前處理,留給子類實現
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
// 解析後處理,留給子類實現
postProcessXml(root);
this.delegate = parent;
}
知識點:
-
preProcessXml和postProcessXml里代碼是空的,這兩個方法是面向子類設計的,設計模式中的模板方法,也可以說是Spring的一個擴展點,後面有機會可以深入下細節。
3.3 parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
功能概述: 判別XML中Bean的聲明標籤是默認的還是自定義的,執行不同的解析邏輯。對於根節點或者子節點是默認命名空間採用parseDefaultElement,否則使用parseCustomElement對自定義命名空間解析。
/**
* DefaultBeanDefinitionDocumentReader
**/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate){
// 對beans的處理
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)) {
// 對默認的Bean標籤解析
parseDefaultElement(ele, delegate);
}
else {
// 對自定義的Bean標籤解析
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
知識點:
-
Spring中XML有兩大類Bean的聲明標籤
-
默認聲明標籤:
<bean id="userService" class="com.fly4j.service.impl.UserServiceImpl"/>
-
自定義聲明標籤: ·
<tx: annotation-driven />
-
-
怎麼區分是默認命名空間還是自定義命名空間?
通過node.getNamespaceURI()獲取命名空間並和Spring中固定的命名空間//www.springframework.org/schema/beans進行比對,如果一致則默認,否則自定義。
3.4 parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)
功能概述: 上面說到Spring標籤包括默認標籤和自定義標籤兩種。解析這兩種方式分別不同。以下就默認標籤進行說明BeanDefinitionParseDelegate對Bean標籤元素的解析過程。
/**
* DefaultBeanDefinitionDocumentReader
**/
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
// import標籤
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(ele);
}
}
知識點:
-
Spring的4種默認標籤舉例:
-
import
<import resource="spring-config.xml"/>
-
alias
<bean id="userService" class="com.fly4j.service.impl.UserServiceImpl"></bean>
<alias name="userService" alias="user" /> -
bean
<beans>
<bean id="userService" class="com.fly4j.service.impl.UserServiceImpl"></bean>
</beans> -
beans
<beans>
<bean id="userService" class="com.fly4j.service.impl.UserServiceImpl"></bean>
</beans>
-
3.5 processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)
功能概述: 在4中默認標籤當中,其中核心的是對bean標籤的解析。以下就對bean標籤的解析來看解析過程。
/**
* DefaultBeanDefinitionDocumentReader
**/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate){
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); // 參考4.1源碼
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); // 參考4.2源碼
try {
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); // 參考5.1源碼
}
...
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
流程解讀:
-
DefaultBeanDefinitionDocumentReader委託BeanDefinitionParseDelegate的parseBeanDefinitionElement方法進行標籤元素的解析。解析後返回BeanDefinitionHolder實例bdHolder,bdHolder實例包含了配置文件中的id、name、alias之類的屬性。 -
返回的bdHolder不為空時,標籤元素如果有自定義屬性和自定義子節點,還需要再次對以上兩個標籤解析。具體邏輯參考4.2小節源碼。 -
解析完成後,對bdHolder進行註冊,使用BeanDefinitionReaderUtils.registerBeanDefinition()方法。具體邏輯參考5.1小節源碼。 -
發出響應事件,通知相關監聽器,bean已經解析完成。
4.1 registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
功能概述: 通過BeanDefinitionParseDelegate對Bean標籤的解析,解析得到id和name這些信息封裝到BeanDefinitionHolder並返回。
/**
* BeanDefinitionParseDelegate
**/
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}
/**
* BeanDefinitionParseDelegate
**/
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
// 解析id屬性
String id = ele.getAttribute(ID_ATTRIBUTE);
// 解析name屬性
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
List<String> aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
}
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
...
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
}
4.2 decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd)
功能概述: 4.1是對默認標籤的屬性解析,那如果標籤有自定義屬性和自定義子節點,這時就要通過decorateBeanDefinitionIfRequired解析這些自定義屬性和自定義子節點。
/**
* BeanDefinitionParseDelegate
**/
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
BeanDefinitionHolder finalDefinition = originalDef;
// Decorate based on custom attributes first.
// 首先對自定義屬性解析和裝飾
NamedNodeMap attributes = ele.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
// Decorate based on custom nested elements.
// 裝飾標籤下的自定義子節點
NodeList children = ele.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
}
return finalDefinition;
}
5.1 registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
功能概述: 通過beanName註冊BeanDefinition至BeanDefinitionMap中去,至此完成Bean標籤的解析,轉換成Bean在容器中的數據結構BeanDefinition.
/**
* BeanDefinitionReaderUtils
**/
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
// 使用beanName註冊BeanDefinition
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
// 使用別名註冊BeanDefiniion
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
/**
* BeanDefinitionParseDelegate
**/
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
...
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
...
this.beanDefinitionMap.put(beanName, beanDefinition);
}else{
if (hasBeanCreationStarted()) {
// beanDefinitionMap是全局變量,會存在並發訪問問題
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}
...
}
五. 結語
總結Spring IOC基礎容器XmlBeanFactory的啟動流程概括如下:
-
執行XmlFactoryBean構造方法,執行加載BeanDefinition方法。 -
使用XmlBeanDefinitionReader讀取器將資源Resource解析成DOM Document對象。 -
使用DefaultBeanDefinitionDocumentReader讀取器從Document對象解析出 Element。 -
通過BeanDefinitionParserDelegate將Element轉換成對應的BeanDefinition。 -
實現BeanDefinitionRegistry註冊器將解析好的BeanDefinition註冊到容器中的BeanDefitionMap里去
本篇就XmlBeanFactory容器啟動流程分析和源碼解析兩個角度來對Spring IOC容器有個基礎的認識。有了這篇基礎,下篇就開始對Spring的擴展容器ApplicationContext進行分析。最後希望大家看完都有所收穫,可以的話給個關注,感謝啦。
六. 附錄
附上我編譯好的Spring源碼,版本是當前最新版本5.3.0,歡迎star