我該如何學習spring源碼以及解析bean定義的註冊

如何學習spring源碼

前言

本文屬於spring源碼解析的系列文章之一,文章主要是介紹如何學習spring的源碼,但願可以最大限度的幫助到有須要的人。文章整體難度不大,但比較繁重,學習時必定要耐住性子堅持下去。node

獲取源碼

源碼的獲取有多種途徑mysql

GitHub

spring-frameworkgit

spring-wikigithub

能夠從GitHub上獲取源代碼,而後自行編譯web

maven

使用過maven的都知道能夠經過maven下載相關的源代碼和相關文檔,簡單方便。spring

這裏推薦經過maven的方式構建一個web項目。經過對實際項目的運行過程當中進行調試來學習更好。sql

如何開始學習

前置條件

若是想要開始學習spring的源碼,首先要求自己對spring框架的使用基本瞭解。明白spring中的一些特性如ioc等。瞭解spring中各個模塊的做用。bash

肯定目標

首先咱們要知道spring框架自己通過多年的發展示在已是一個龐大的家族。可能其中一個功能的實現依賴於多個模塊多個類的相互配合,這樣會致使咱們在閱讀代碼時難度極大。多個類之間進行跳躍很容易讓咱們暈頭轉向。markdown

因此在閱讀spring的源代碼的時候不能像在JDK代碼時一行一行的去理解代碼,須要把有限的精力更多的分配給重要的地方。並且咱們也沒有必要這樣去閱讀。mvc

在閱讀spring某一功能的代碼時應當從一個上帝視角來總覽全局。只須要知道某一個功能的實現流程便可,並且幸運的是spring的代碼規範較好,大多數方法基本都能見名知意,這樣也省去了咱們不少的麻煩。

利用好工具

閱讀代碼最好在idea或者eclipse中進行,這類IDE提供的不少功能頗有幫助。

在閱讀時配合spring文檔更好(若是是自行編譯源碼直接看註釋更好)。

筆記和複習

這個過程及其重要,我之前也看過一些spring的源碼,可是好幾回都是感受比較吃力在看過一些後就放棄了。而因爲沒有作筆記和沒有複習的緣由很快就忘了。下次想看的時候還要從新看一遍,很是的浪費時間。

下面以IOC爲例說明下我是怎麼看的,供參考。

IOC

入口:ApplicationContext

在研究源碼時首先要找到一個入口,這個入口怎麼選擇能夠本身定,當必定要和你須要看的模塊有關聯。

好比在IOC中,首先咱們想到建立容器是在什麼過程?

在程序啓動的時候就建立了,並且在啓動過程當中大多數的bean實例就被注入了。

那問題來了,在啓動的時候是從那個類開始的呢?熟悉spring的應該都知道咱們平時在作單元測試時若是要獲取bean實例,一個是經過註解,另外咱們還能夠經過構建一個ApplicationContext來獲取:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath*:application.xml");
	XxxService xxxService = applicationContext.getBean("xxxService");
複製代碼

在實例化ApplicationContext後既能夠獲取bean,那麼實例化的這個過程就至關於啓動的過程了,因此咱們能夠將ApplicationContext當成咱們的入口。

ApplicationContext是什麼

首先咱們要明白的事咱們平時一直說的IOC容器在Spring中實際上指的就是ApplicationContext

若是有看過我以前手寫Spring系列文章的同窗確定知道在當時文章中充當ioc容器的是BeanFactory,每當有bean須要注入時都是由BeanFactory保存,取bean實例時也是從BeanFactory中獲取。

那爲何如今要說ApplicationContext纔是IOC容器呢?

由於在spring中BeanFactory其實是被隱藏了的。ApplicationContext是對BeanFactory的一個封裝,也提供了獲取bean實例等功能。由於BeanFactory自己的能力實在太強,若是可讓咱們隨便使用可能會對spring功能的運行形成破壞。因而就封裝了一個提供查詢ioc容器內容的ApplicationContext供咱們使用。

若是項目中須要用到ApplicationContext,能夠直接使用spring提供的註解獲取:

@Autowired
	private ApplicationContext applicationContext;
複製代碼

如何使用ApplicationContext

若是咱們要使用ApplicationContext能夠經過new該類的一個實例便可,定義好相應的xml文件。而後經過下面的代碼便可:

@Test
    public void testClassPathXmlApplicationContext() {
        //1.準備配置文件,從當前類加載路徑中獲取配置文件
        //2.初始化容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath*:application.xml");
        //二、從容器中獲取Bean
        HelloApi helloApi = applicationContext.getBean("hello", HelloApi.class);
        //三、執行業務邏輯
        helloApi.sayHello();
    }
複製代碼

ApplicationContext的體系

瞭解一個類,首先能夠來看看它的繼承關係來了解其先天的提供哪些功能。而後在看其自己又實現了哪些功能。

ApplicationContext繼承體系

上圖中繼承關係從左至右簡要介紹其功能。

  • ApplicationEventPublisher:提供發佈監聽事件的功能,接收一個監聽事件實體做爲參數。須要瞭解的能夠經過這篇文章:事件監聽
  • ResourcePatternResolver:用於解析一些傳入的文件路徑(好比ant風格的路徑),而後將文件加載爲resource。
  • HierarchicalBeanFactory:提供父子容器關係,保證子容器能訪問父容器,父容器沒法訪問子容器。
  • ListableBeanFactory:繼承自BeanFactory,提供訪問IOC容器的方法。
  • EnvironmentCapable:獲取環境變量相關的內容。
  • MessageSource:提供國際化的message的解析

配置文件的加載

Spring中每個功能都是很大的一個工程,因此在閱讀時也要分爲多個模塊來理解。要理解IOC容器,咱們首先須要瞭解spring是如何加載配置文件的。

縱覽大局

idea或者eclipse提供了一個很好的功能就是能在調試模式下看到整個流程的調用鏈。利用這個功能咱們能夠直接觀察到某一功能實現的總體流程,也方便在閱讀代碼時在不一樣類切換。

以加載配置文件爲例,這裏給出整個調用鏈。

配置文件加載流程

上圖中下面的紅框是咱們寫的代碼,即就是咱們應該開始的地方。下面的紅框就是加載配置文件結束的地方。中間既是總體流程的實現過程。在閱讀配置文件加載的源碼時咱們只須要關心這一部分的內容便可。

須要知道的是這裏展現出來的僅僅只是跟這個過程密切相關的一些方法。實際上在這個過程當中還有須要的方法被執行,只不過執行完畢後方法棧彈出因此不顯示在這裏。不過大多數方法都是在爲這個流程作準備,因此基本上咱們也不用太在乎這部份內容

refresh()

前面的關於ClassPathXmlApplicationContext的構造函數部分沒有啥好說的,在構造函數中調用了一個方法AbstractApplicationContext#refresh。該方法很是重要,在建立IOC容器的過程當中該方法基本上是全程參與。主要功能爲用於加載配置或這用於刷新已經加載完成的容器配置。經過該方法能夠在運行過程當中動態的加入配置文件等:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
    ctx.setConfigLocation("application-temp.xml");
    ctx.refresh();
複製代碼

AbstractApplicationContext#refresh

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			prepareRefresh();

			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			// more statement ...
		}
	}
複製代碼

這裏將於當前功能不相關的部分刪除掉了,能夠看到進入方法後就會進入一個同步代碼塊。這是爲了防止在同一時間有多個線程開始建立IOC容器形成重複實例化。

prepareRefresh();方法主要用於設置一些日誌相關的信息,好比容器啓動時間用於計算啓動容器總體用時,以及設置一些變量用來標識當前容器已經被激活,後續不會再進行建立。

obtainFreshBeanFactory();方法用於獲取一個BeanFactory,在這一過程當中便會加載配置文件和解析用於生成一個BeanFactory。

refreshBeanFactory

refreshBeanFactory方法有obtainFreshBeanFactory方法調用

protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}
複製代碼

該方法首先判斷是否已經實例化好BeanFactory,若是已經實例化完成則將已經實例化好的BeanFactory銷燬。

而後經過new關鍵字建立一個BeanFactory的實現類實例,設置好相關信息。customizeBeanFactory(beanFactory)方法用於設置是否運行當beanName重複是修改bean的名稱(allowBeanDefinitionOverriding)和是否運行循環引用(allowCircularReferences)。

loadBeanDefinitions(beanFactory)方法既是開始加載bean定義的方法。當BeanFactory在加載完全部配置信息後建立,而後將建立好的BeanFactory賦值給當前context下的BeanFactory。

loadBeanDefinitions

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}
複製代碼

loadBeanDefinitions見名知意其就是用於加載bean定義的方法,在AbstractXmlApplicationContext中定義了一系列該方法的重載方法。上面的方法主要即是引入XmlBeanDefinitionReaderXmlBeanDefinitionReader是一個用於讀取xml文件中bean定義的類,其提供了一些諸如BeanFactory和BeanDefinitionRegistery類的屬性以供使用。但其實真正的讀取操做並沒該類完成,其也是做爲一個代理存在。

在spring中若是是完成一些相似操做的類的命名都是有跡可循的,好比這裏讀取xml文件就是以reader結尾,相似的讀取註解中bean定義也有如AnnotatedBeanDefinitionReader。若是須要向類中注入一些Spring中的bean,通常是以Aware結尾如BeanFactoryAware等。因此在閱讀spring源碼時若是遇到這樣的類不少時候咱們能夠直接根據其命名瞭解其大概的實現方式。

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
		}

		if (resourceLoader instanceof ResourcePatternResolver) {
			try {
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				int loadCount = loadBeanDefinitions(resources);
				if (actualResources != null) {
					for (Resource resource : resources) {
						actualResources.add(resource);
					}
				}
                //logging
				return loadCount;
			}catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}else {
			Resource resource = resourceLoader.getResource(location);
			int loadCount = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
            //logging
			return loadCount;
		}
	}
複製代碼

上面代碼是loadBeanDefinitions的一個實現類,該方法的主要注意點在於三個地方。

一個是方法中拋出的兩個異常,前一個異常時由於ResourceLoader定義的問題,通常來講不須要咱們關注。後一個就是配置文件出錯了,多是由於文件自己xml格式出錯或者是因爲循環引用等緣由,具體的緣由也會經過日誌打印。咱們須要對這些異常信息有印象,也不用刻意去記,遇到了能快速定位問題便可。

另外一個就是代碼中的一個if(){}else{}語句塊,判斷語句快中都是用於解析配置文件,不一樣之處在於if中支持解析匹配風格的location,好比classpath*:spring.xml這種,該功能的實現由ResourcePatternResolver提供,ResourcePatternResolverResourceLoader的功能進行了加強,支持解析ant風格等模式的location。而else中僅僅只能解析指定的某一文件如spring.xml這種。實際上在ApplicationContext中實現了ResourcePatternResolver,若是也按照spring.xml配置,也是按照ResourceLoader提供的解析方式解析。

最後一處就是Resource類,Resource是spring爲了便於加載文件而特地設計的接口。其提供了大量對傳入的location操做方法,支持對不一樣風格的location(好比文件系統或者ClassPath)。其自己還有許多不一樣的實現類,本質上是對File,URL,ClassPath等不一樣方式獲取location的一個整合,功能十分強大。即便咱們的項目不依賴spring,若是涉及到Resource方面的操做也可使用Spring中的Resource。

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        //log and assert
		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<EncodedResource>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}
複製代碼

該方法依舊是loadBeanDefinitions的重載方法。

方法傳入一個EncodedResource,該類能夠經過制定的字符集對Resource進行編碼,利於統一字符編碼格式。

而後try語句塊上面的代碼也是比較重要的,主要功能即是判斷是否有配置文件存在循環引用的問題。

循環應用問題出如今好比我加載一個配置文件application.xml,可是在該文件內部又經過import標籤引用了自身。在解析到import時會加載import指定的文件。這樣就形成了一個死循環,若是不解決程序就會永遠啓動不起來。

解決的方法也很簡單,經過一個ThreadLocal記錄下當前正在加載的配置文件名稱(包括路徑),每一次在加載新的配置文件時從ThreadLocal中取出放入到set集合中,經過set自動去重的特性判斷是否循環加載了。當一個文件加載完成後,就從ThreadLocal中去掉(finally)。這裏是判斷xml文件時否重複加載,而在spring中判斷bean是否循環引用是雖然實現上有點差異,但基本思想也是這樣的。

doLoadBeanDefinitions(InputSource, Resource)

到了這一步基本上纔算是真正開始解析了。該方法雖然代碼行數較多,可是大多都是異常處理,異常代碼已經省略。咱們須要關注的就是try中的兩句代碼。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			Document doc = doLoadDocument(inputSource, resource);
			return registerBeanDefinitions(doc, resource);
		}catch (Exception ex) {
			//多個catch語句塊
		}
	}
複製代碼

Document doc = doLoadDocument(inputSource, resource)就是讀取配置文件並將其內容解析爲一個Document的過程。解析xml通常來講並不須要咱們特別的去掌握,稍微有個瞭解便可,spring這裏使用的解析方式爲Sax解析,有興趣的能夠直接搜索相關文章,這裏不進行介紹。下面的registerBeanDefinitions纔是咱們須要關注的地方。

registerBeanDefinitions(Document, Resource)

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		documentReader.setEnvironment(getEnvironment());
		int countBefore = getRegistry().getBeanDefinitionCount();
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}
複製代碼

在進入該方法後首先建立了一個BeanDefinitionDocumentReader的實例,這和以前的用於讀取xml的reader類同樣,只不過該類是用於從xml文件中讀取BeanDefinition

Environment

在上面的代碼中給Reader設置了Environment,這裏談一下關於Environment。

Environment是對spring程序中涉及到環境有關的一個描述集合,主要分爲profile和properties。

profile是一組bean定義的集合,經過profile能夠指定不一樣的配置文件用以在不一樣的環境中,如測試環境,生產環境的配置分開。在部署時只須要配置好當前所處環境值便可按不一樣分類加載不一樣的配置。

profile支持xml配置和註解的方式。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    //more >

    <!-- 定義開發環境的profile -->
    <beans profile="development">
        <!-- 只掃描開發環境下使用的類 -->
        <context:component-scan base-package="com.demo.service" />
        <!-- 加載開發使用的配置文件 -->
        <util:properties id="config" location="classpath:dev/config.properties"/>
    </beans>

    <!-- 定義生產環境的profile -->
    <beans profile="produce">
        <!-- 只掃描生產環境下使用的類 -->
        <context:component-scan
            base-package="com.demo.service" />
        <!-- 加載生產使用的配置文件 -->    
        <util:properties id="config" location="classpath:produce/config.properties"/>
    </beans>
</beans>
複製代碼

也能夠經過註解配置:

@Service
@Profile("dev")
public class ProductRpcImpl implements ProductRpc {
    public String productBaseInfo(int id) {
        return "success";
    }
}
複製代碼

而後在啓動時根據傳入的環境值加載相應的配置。

properties是一個很寬泛的定義,其來源不少如properties文件,JVM系統變量,系統環境變量,JNDI,servlet上下文參數,Map等。spring會讀取這些配置並在environment接口中提供了方便對其進行操做的方法。

總之就是設計到跟環境有關的直接來找Environment便可。

handler

代碼接着往下走,documentReader.registerBeanDefinitions(doc, createReaderContext(resource))這一步很明顯就是從解析好的document對象中讀取BeanDefinition的過程,可是在此以前咱們先要關注一下createReaderContext(resource)方法。

先來看一個XML文件。

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc" >
</beans>
複製代碼

上面是xml中根元素定義部分,可能平時並無太多人注意。其屬性中的xmlns是XML NameSpace的縮寫。namespace的做用主要是爲了防止在xml定義的節點存在衝突的問題。好比上面聲明瞭mvc的namespace:xmlns:mvc="http://www.springframework.org/schema/mvc"。在xml文件中咱們就可使用mvc了:

<mvc:annotation-driven />
	<mvc:default-servlet-handler/>
複製代碼

而實際上在spring中還根據上面定義的namespace來準備了各自的處理類。這裏由於解析過程就是將xml定義的每個節點取出根據配置好的屬性和值來初始化或註冊bean,爲了保證代碼可讀性和明確的分工,每個namespace經過一個專有的handler來處理。

跟蹤createReaderContext(resource)方法,最終來到DefaultNamespaceHandlerResolver類的構造方法中。

handler匹配

public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
		//DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers"
		this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
	}
複製代碼

能夠看到默認的handler是經過一個本地文件來進行映射的。該文件存在於被依賴jar包下的META-INF文件夾下的spring.handlers文件中。

handler mapping

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
複製代碼

這裏只是展現了beans包下的映射文件,其餘如aop包,context包下都有相應的映射文件。經過讀取這些配置文件來映射相應的處理類。在解析xml時會根據使用的namespace前綴使用對應的handler類解析。這種實現機制其實就是所謂的SPI(Service Provider Interface),目前不少的應用都在實現過程當中使用了spi,如dubbo,mysql的jdbc實現等,有興趣的能夠取了解一下。

doRegisterBeanDefinitions(Element)

到這一步中間省略了一個方法,很簡單沒有分析的必要。

protected void doRegisterBeanDefinitions(Element root) {
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);
		if (this.delegate.isDefaultNamespace(root)) {
			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);
		parseBeanDefinitions(root, this.delegate);
		postProcessXml(root);

		this.delegate = parent;
	}
複製代碼
delegate

這裏的delegate是對BeanDefinitionParse功能的代理,提供了一些支持解析過程的方法。咱們能夠看到上面有一個從新建立delegate同時又將以前的delegate保存的代碼。註釋上說是爲了防止嵌套的beans標籤遞歸操做致使出錯,可是註釋後面又說這並不須要這樣處理,這個操做真的看不懂了,實際上我認爲即便遞歸應該也是沒有影響的。仍是我理解錯了?

建立好delegate後下面的if語句塊就是用來判斷當前加載的配置文件是不是當前使用的profile指定的配置文件。上面在介紹Environment的時候已經介紹過來,若是這裏加載的配置文件和profile指定的不符則直接結束。

preProcessXml(root)方法是一個空實現,而且當前spring框架中好像也沒有對這個方法實現的,這裏無論了。同理還有下面的postProcessXml(root)

parseBeanDefinitions(Element, BeanDefinitionParserDelegate)

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		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)) {
						parseDefaultElement(ele, delegate);
					}
					else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}
複製代碼

該方法的理解起來並不難,首先讀取根節點(beans)下的全部子節點,而後對這些節點進行解析。這裏須要注意的即便是對節點的解析也有一個判斷語句。

主要來看一下delegate.isDefaultNamespace(ele),

public boolean isDefaultNamespace(String namespaceUri) {
        //BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans"
		return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
	}
複製代碼

也就是說beans命名空間下的標籤一個解析方法,而另外的標籤一個解析方法。

parseDefaultElement(Element, BeanDefinitionParserDelegate)

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)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}
複製代碼

能夠看到對於每個標籤,都提供了一個方法來進行解析,最後一個方法用於對嵌套標籤進行解析,這裏以bean標籤的解析爲例。

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
		    //用於將一些屬性值塞進BeanDefinition中如lazy-init
		    //以及子節點中的值 如bean節點下的property
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}
複製代碼

Holder對象也是spring中的一個系列性的對象,主要就是對某一些實例進行包裝,好比BeanDefinitionHolder就是對BeanDefinition進行包裝,主要就是持有BeanDefinition以及它的名稱和別名等(BeanDefinition爲接口,沒法提供名稱等屬性)。

public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}
複製代碼

接下來的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())能夠算是咱們本次目標最重要的一步了,前面全部的流程都是給這一步鋪墊。經過該方法將解析出來的BeanDefinition註冊到容器中,方便實例化。

方法接收兩個參數holder和registry,若是有看過我手寫系列的文章(IOC篇)應該知道,當時爲了將bean定義和容器關聯起來以及爲了將beanfactory的功能簡化,因此咱們定義了一個BeanDefinitionRegistry接口用於將BeanDefinition註冊到容器中和從容器中取BeanDefinition,這裏的registry功能也是同樣的。(BeanDefinitionRegistryDefaultListableBeanFactory實現,而DefaultListableBeanFactory實際就是容器)

並且能夠看到實際上這裏也是經過beanName來區分BeanDefinition是否重複(實際上確定是我仿的spring的(笑)),只不過爲了運行名稱相同的BeanDefinition註冊提供了alias,以前在實現ioc時沒有實現這一步。

processBeanDefinition方法的最後一步其實是註冊了一個listener,在一個BeanDefinition被註冊後觸發,只不過上spring中實際觸發方法是一個空方法,若是咱們須要在BeanDefinition註冊完成後作一些什麼工做能夠直接繼承EmptyReaderEventListener後實現componentRegistered(componentDefinition)方法便可。

到這裏基本上關於BeanDefinition的加載就完成了,後面就是重複上面的流程加載多個配置文件。

小結

本節主要介紹了我關於學習spring源碼的一些方法,以及以spring的BeanDefinition的加載爲例分析了其總體的流程,但願對你們能有所幫助。還要提的是spring源碼很複雜,若是隻是開斷點一路調試下去確定是不夠的,看的過程當中須要多作筆記。因爲該文章內容較多以及本人水平問題,文章中可能會存在錯誤,若是有能夠指出來方便修改。

相關文章
相關標籤/搜索