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啟動流程的先后順序整理的關鍵性代碼索引列表,其中一級索引為類,二級索引對應其類下的方法。符號 ---?表示接口的實現。建議可以觀察方法索引的參數變化(資源轉換)來分析整個流程,來加深對流程的理解。

  1. XmlBeanFactory

    1. XmlBeanFactory(Resource resource)
  2. XmlBeanDefinitionReader

    1. loadBeanDefinitions(Resource resource)
    2. doLoadBeanDefinitions(InputSource inputSource, Resource resource)
    3. registerBeanDefinitions(Document doc, Resource resource)
  3. DefaultBeanDefinitionDocumentReader ---? BeanDefinitionDocumentReader

    1. registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
    2. doRegisterBeanDefinitions(Element root)
    3. parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
    4. parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)
    5. processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)
  4. BeanDefinitionParserDelegate

    1. parseBeanDefinitionElement(Element ele)
    2. decorateBeanDefinitionIfRequired( Element ele, BeanDefinitionHolder originalDef,...)
  5. DefaultListableBeanFactory ---? BeanDefinitionRegistry

    1. registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

好了,當你心里對這個流程有個大概的樣子之后,這里就可以繼續走下去。建議還是很模糊的同學自重,避免走火入魔想對我人身攻擊的。下面就每個方法重要的點一一解析,嚴格按照方法的執行先后順序來說明。

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);
}

知識點:

  1. 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());
    }
    ...
}

知識點:

  1. 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;
        }
    ...
}

知識點:

  1. 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;
}

知識點:

  1. BeanDefinitionDocumentReader是接口,實際完成對DOM Document解析的是其默認實現類DefaultBeanDefinitionDoucumentReade。
  2. 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;
}

知識點:

  1. 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);
	}
}

知識點:

  1. Spring中XML有兩大類Bean的聲明標簽

    1. 默認聲明標簽:

            <bean id="userService" class="com.fly4j.service.impl.UserServiceImpl"/>
      
    2. 自定義聲明標簽: ·

            <tx: annotation-driven />
      
  2. 怎么區分是默認命名空間還是自定義命名空間?

    通過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);
	}
}

知識點:

  1. Spring的4種默認標簽舉例:

    1. import

       <import resource="spring-config.xml"/>
      
    2. alias

      <bean id="userService" class="com.fly4j.service.impl.UserServiceImpl"></bean>
      <alias name="userService" alias="user" />
      
    3. bean

      <beans>
      	<bean id="userService" class="com.fly4j.service.impl.UserServiceImpl"></bean>
      </beans>
      
    4. 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));
	}
}

流程解讀:

  1. DefaultBeanDefinitionDocumentReader委托BeanDefinitionParseDelegate的parseBeanDefinitionElement方法進行標簽元素的解析。解析后返回BeanDefinitionHolder實例bdHolder,bdHolder實例包含了配置文件中的id、name、alias之類的屬性。
  2. 返回的bdHolder不為空時,標簽元素如果有自定義屬性和自定義子節點,還需要再次對以上兩個標簽解析。具體邏輯參考4.2小節源碼。
  3. 解析完成后,對bdHolder進行注冊,使用BeanDefinitionReaderUtils.registerBeanDefinition()方法。具體邏輯參考5.1小節源碼。
  4. 發出響應事件,通知相關監聽器,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的啟動流程概括如下:

  1. 執行XmlFactoryBean構造方法,執行加載BeanDefinition方法。
  2. 使用XmlBeanDefinitionReader讀取器將資源Resource解析成DOM Document對象。
  3. 使用DefaultBeanDefinitionDocumentReader讀取器從Document對象解析出 Element。
  4. 通過BeanDefinitionParserDelegate將Element轉換成對應的BeanDefinition。
  5. 實現BeanDefinitionRegistry注冊器將解析好的BeanDefinition注冊到容器中的BeanDefitionMap里去

本篇就XmlBeanFactory容器啟動流程分析和源碼解析兩個角度來對Spring IOC容器有個基礎的認識。有了這篇基礎,下篇就開始對Spring的擴展容器ApplicationContext進行分析。最后希望大家看完都有所收獲,可以的話給個關注,感謝啦。

六. 附錄

附上我編譯好的Spring源碼,版本是當前最新版本5.3.0,歡迎star

spring-framework-5.3.0編譯源碼

posted @ 2020-05-18 00:33  你好,舊時光  閱讀(...)  評論(...編輯  收藏
全民捕鱼游戏怎么玩 广西彩票11选五查询 福彩3d首页 极速赛车手急速赛车 注销pc蛋蛋 轴研科技股票 江西十一选五手机版彩乐乐 湖北快3和值走势图 幸运农场水果开奖直播 北京11选5走势一定牛 浙江十一选五