一. 前言
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中固定的名稱空間http://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