1. 程式人生 > 實用技巧 >Spring詳解(五)——Spring IOC容器的初始化過程

Spring詳解(五)——Spring IOC容器的初始化過程

1、前言

上一章介紹了Spring IOC容器的設計與實現,同時也講到了高階容器ApplicationContext中有個refresh()方法,執行了這個方法標誌著 IOC 容器正式啟動,簡單來說,IOC 容器的初始化是由refresh()方法來啟動的。而在Spring IOC 容器啟動的過程中,會將Bean解析成Spring內部的BeanDefinition結構。不管是通過xml配置檔案的<Bean>標籤,還是通過註解配置的@Bean,它最終都會被解析成一個BeanDefinition資訊物件,最後我們的Bean工廠就會根據這份Bean的定義資訊,對Bean進行例項化、初始化等等操作。

從上可知BeanDefinition這個物件對Spring IoC容器的重要之處,並且IOC的初始化都是圍繞這個BeanDefinition來進行的。所以瞭解好了它,能讓我們更大視野的來看Spring管理Bean的一個過程,也能透過現象看本質。所以這裡再次強調一次BeanDefinition物件的作用:簡單來說,BeanDefinition在Spring中是用來描述Bean物件的,它本身並不是一個Bean例項,而是包含了Bean例項的所有資訊,比如類名、屬性值、構造器引數、scope、依賴的bean、是否是單例類、是否是懶載入以及其它資訊。其實就是將Bean例項定義的資訊儲存到這個BeanDefinition相應的屬性中,後面Bean物件的建立是根據BeanDefinition中描述的資訊來建立的,例如拿到這個BeanDefinition後,可以根據裡面的類名、建構函式、建構函式引數,使用反射進行物件建立。也就是說 IOC容器可以有多個BeanDefinition,並且一個BeanDefinition物件對應一個<bean>標籤中的資訊。當然BeanDefinition的最終目的不只是用來儲存Bean例項的所有資訊,而是為了可以方便的進行修改屬性值和其他元資訊,比如通過BeanFactoryPostProcessor進行修改一些資訊,然後在建立Bean物件的時候就可以結合原始資訊和修改後的資訊建立物件了。

2、IOC容器的初始化步驟

我們知道,在refresh()之後IOC 容器的啟動會經過一段很複雜的過程,我們暫時不要求全部瞭解清楚,但是現在大體瞭解一下 Spring IoC 初始化的過程還是必要的。這對於理解 Spring 的一系列行為是很有幫助的。IOC 容器初始化包括BeanDefinition的Resource定位、載入和註冊三個基本過程,如果我們瞭解如何程式設計式的使用 IOC 容器(程式設計式就是使用DefaultListableBeanFactory來建立容器),就可以清楚的看到Resource定義和載入過程的介面呼叫,在下面的內容中,我們將會詳細分析這三個過程的實現。

IOC 容器的初始化包括的三個過程介紹如下:

  1. Resource定位過程:這個Resource定位指的是BeanDefinition的資源定位,就是對開發者的配置檔案(Xml)進行資源的定位,並將其封裝成Resource物件。它由ResourceLoader通過統一的Resource介面來完成,這個Resource對各種形式的BeanDefinition的使用都提供了統一介面。比如:在檔案系統中的Bean定義資訊可以使用FileSystemResource來進行抽象。在類路徑中的Bean定義資訊可以使用ClassPathResource來進行抽象等等。這個定位過程類似於容器尋找資料的過程,就像用水捅裝水先要把水找到一樣。
  2. BeanDefinition的載入:這個載入過程是將Resource 定位到的資訊,表示成IoC容器內部的資料結構,而這個容器內部的資料結構就是BeanDefinition。
  3. BeanDefinition的註冊:這個註冊過程把上面載入過程中解析得到的BeanDeftnition向IoC容器進行註冊。註冊過程是通過呼叫BeanDefinitionRegistry介面的實現來完成的。在IoC容器內部將BeanDefinition注人到一個HashMap中去,IoC容器就是通過這個HashMap來持有這些BeanDefinition資料的。

注意:Bean的定義和初始化在 Spring IoC 容器是兩大步驟,它是先定義,然後再是初始化和依賴注入。所以當Spring做完了以上 3 步後,Bean 就在 Spring IoC 容器中被定義了,而沒有被初始化,更沒有完成依賴注入,所以此時仍然沒有對應的 Bean 的例項,也就是沒有注入其配置的資源給 Bean,也就是它還不能完全使用。對於初始化和依賴注入,Spring Bean 還有一個配置選項——【lazy-init】,其含義就是:是否預設初始化 Spring Bean。在沒有任何配置的情況下,它的預設值為default,實際值為 false(預設非懶載入),也就是 Spring IoC 容器預設會自動初始化 Bean。如果將其設定為 true(懶載入),那麼只有當我們使用 Spring IoC 容器的 getBean 方法獲取它時,它才會進行 Bean 的初始化,完成依賴注入。

3、BeanDefinition的Resource定位

在Spring框架中,如果想要獲取系統中的配置檔案,就必須通過Resource介面的實現來完成,Resource是Sping中用於封裝I/O操作的介面。例如我們前面在以程式設計的方式使用DefaultListableBeanFactory時,首先是定義一個Resource來定位容器使用的BeanDefinition,這裡使用的是Resource的實現類ClassPathResource,這時Spring會在類路徑中去尋找以檔案形式存在BeanDefinition。

ClassPathResource resource = new ClassPathResource("beans.xml");

但是這裡的Resource並不能由 DefaultListableBeanFactory 直接使用,而是需要通過Spring中的 BeanDefinitionReader 來對這些資訊進行處理。在這裡,我們也可以看到使用 ApplicationContext 相對於直接使用 DefaultListableBeanFactory 的好處,因為在ApplicationContext中,Spring已經為我們提供了一系列載入不同Resource的讀取器實現,而在 DefaultListableBeanFactory 只是一個純粹的IOC容器,需要為它配置配置特定的讀取器才能完成這些功能,當然了 利和弊 是共存的,使用 DefaultListableBeanFactory 這樣更底層的IOC容器,能提高定製IOC容器的的靈活性。

常用的Resource資源型別如下:

  • FileSystemResource:以檔案的絕對路徑方式進行訪問資源,效果類似於Java中的File;
  • ClassPathResourcee:以類路徑的方式訪問資源,效果類似於this.getClass().getResource("/").getPath();
  • ServletContextResource:web應用根目錄的方式訪問資源,效果類似於request.getServletContext().getRealPath("");
  • UrlResource:訪問網路資源的實現類。例如file: http: ftp:等字首的資源物件;
  • ByteArrayResource: 訪問位元組陣列資源的實現類。

回到我們經常使用的ApplicationContext上來,它給我們提供了一系列載入不同Resource的讀取器實現,例如ClassPathXmlApplicationContext、FileSystemXmlApplicationContext以及XmlWebApplicationContext等等,簡單的從這些類的名字上分析,可以清楚的看到他們可以提供哪些不同的Resource讀入功能,比如:ClassPathXmlApplicationContext可以從 classpath載入Resource,FileSystemXmlApplicationContext可以從檔案系統中載入Resource,XmlWebApplicationContext可以在Web容器中載入Resource等。

我們通常喜歡拿ClassPathXmlApplicationContext來舉例,所以這裡用它來分析ApplicationContext是如何來完成BeanDefinition的Resource定位,首先來看一下ClassPathXmlApplicationContext的整繼承體系:

通過上面的圖片並且檢視繼承關係可知,ClassPathXmlApplicationContext繼承了AbstractApplicationContext,所以該實現類具備了讀取Resource定義的BeanDefinition的能力。因為AbstractApplicationContext的基類是DefaultResourceLoader。而且其它的類如FileSystemXmlApplicationContext、XmlWebApplicationContext等等都如出一轍。也是通過DefaultResourceLoader讀取Resource。

下面我們再來看一下ClassPathXmlApplicationContext的順序圖。通過這個順序圖可以清晰的看到IOC容器的初始化階段所呼叫的各個方法。


那麼接下來我們從ClassPathXmlApplicationContext這個類來分析Spring的IoC容器是如何一步一步完成定位的:

①、我們知道IOC容器的啟動是從refresh()方法開始的,所以我們先從refresh()方法開始:ClassPathXmlApplicationContext類中呼叫的refresh()方法是其繼承的基類 AbstractApplicationContext中的實現,所以先跟蹤AbStractApplicationContext中的refresh()方法:

注意:在refresh()中我們先重點看obtainFreshBeanFactory()這個方法,這是IoC容器初始化的入口。

public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
	
		//重新整理上下文環境
		prepareRefresh();
		
		//我們先著重看這個方法 這是初始化容器的地方,是在子類中啟動refreshBeanFactory()
		//並且在這裡獲得新的BeanFactory,解析XML、Java類,並載入BeanDefinition
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		//準備bean工廠,以便在此上下文中使用
		prepareBeanFactory(beanFactory);

		try {
			//設定 beanFactory 的後置處理
			postProcessBeanFactory(beanFactory);
			//呼叫 BeanFactory 的後處理器,這些處理器是在Bean 定義中向容器註冊的
			invokeBeanFactoryPostProcessors(beanFactory);
			//註冊Bean的後處理器,在Bean建立過程中呼叫
			registerBeanPostProcessors(beanFactory);
			//對上下文中的訊息源進行初始化
			initMessageSource();
			//初始化上下文中的事件機制
			initApplicationEventMulticaster();
			//初始化其他特殊的Bean
			onRefresh();
			//檢查監聽Bean並且將這些監聽Bean向容器註冊
			registerListeners();
			//例項化所有的(non-lazy-init)單件
			finishBeanFactoryInitialization(beanFactory);
			//釋出容器事件,結束Refresh過程
			finishRefresh();
		}

		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}
			destroyBeans();
			cancelRefresh(ex);
			throw ex;
		}
		finally {
			//重置Spring公共的快取
			resetCommonCaches();
		}
	}
}


②、然後點選obtainFreshBeanFactory()這個方法,它還在AbstractApplicationContext中實現,這個obtainFreshBeanFactory()很關鍵,這裡面有 IoC的Resource定位和載入。

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
	refreshBeanFactory();
	return getBeanFactory();
}

進來後發現其呼叫refreshBeanFactory和getBeanFactory方法,表示重新獲取一個新的BeanFactory例項。


③、繼續跟蹤refreshBeanFactory()方法,點選進入。

	protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

可以看到這裡只是定義了抽象方法,既然是抽象的方法,那麼肯定有具體的實現,那這個具體初始化IOC容器的實現在哪呢?在AbstractApplicationContext中沒有做具體實現。我們從前面的繼承圖可知,AbstractApplicationContext還有很多子類,所以肯定是交給其子類完成,實現解耦,讓初始化IOC容器變得更加靈活。

所以我們從其子類AbstractRefreshableApplicationContext中找到實現的refreshBeanFactory()方法。

	protected final void refreshBeanFactory() throws BeansException {
		//這裡判斷,如果存在了BeanFactory,則銷燬並關閉該BeanFactory
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			//這裡的建立新的BeanFactory,對於DefaultListableBeanFactory前面一章已經介紹了很多了,應該都知道它的作用
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			//載入Bean ,抽象方法,委託子類AbstractXmlApplicationContext實現
			//後面會看到一系列過載的loadBeanDefinitions方法
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

上面的程式碼主要分為這麼幾個步驟:

  1. 首先判斷BeanFactory是否存在,如果存在(不為NULL),則銷燬關閉該BeanFactory。也就是清除跟Bean有關的Map或者List等屬性集合,並且將BeanFactory設定為null,序列化Id設定為null。
  2. 然後建立一個新的DefaultListableBeanFactory(這個類是Spring Bean初始化的核心類),所以我們看下建立DefaultListableBeanFactory的地方:createBeanFactory(),這個方法 是在AbstractRefreshableApplicationContext中實現,所以AbstractApplicationContext 讓我們可以充分自由的例項化自己想初始化的原始IOC容器。
  3. protected DefaultListableBeanFactory createBeanFactory() {
            //getInternalParentBeanFactory 獲取當前容器已有的父親容器,來作為新容器的父容器,這個方法是在AbstractApplicationContext中實現的。
            return new DefaultListableBeanFactory(getInternalParentBeanFactory());
        }
    
  4. 最後對新建的BeanFactory進行設定,包括bean序列化Id的設定、bean的特殊設定,bean載入操作。然後將beanFactory賦值給本類的beanFactory屬性。注意:customizeBeanFactory(beanFactory)裡面只做了兩件事:一個是設定bean是否允許覆蓋,另一個是設定bean是否允許循壞使用。


④、跟蹤loadBeanDefinitions(beanFactory)方法。

	protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
			throws BeansException, IOException;

這個方法的具體實現是由子類AbstractXmlApplicationContext具體實現的。所以我們知道了該怎麼去找這個loadBeanDefinitions的具體實現了吧。

	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		//建立一個xml配置讀寫器用於解析xml檔案中定義的bean
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		//設定BeanDefinitionReader 的相關屬性
		//1.設定 Environment,即環境,與容器的環境一致
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		//2.設定 ResourceLoader,即資源載入器,具體載入資源的功能,這個載入器很重要,後面會用到
		//  這裡傳一個this進去,因為ApplicationContext是實現了ResourceLoader介面
		beanDefinitionReader.setResourceLoader(this);
		//3.設定 EntityResolver,即實體解析器,這裡用於解析資源載入器載入的資源內容
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		//這個方法預設實現是空的,允許使用者自定義實現讀取器的定製化,需要實現介面,可以設定xml解析完成校驗,定製化解析器等
		initBeanDefinitionReader(beanDefinitionReader);
		// 這裡開始就是 載入、獲取BeanDefinition資源定位,並且是載入模組的開始了
		loadBeanDefinitions(beanDefinitionReader);
	}


⑤、繼續跟蹤loadBeanDefinitions(beanDefinitionReader)方法,這個方法在AbstractXMLApplicationContext中有實現,我們看下。

	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		//以Resource的方式獲取所有定位到的resource資源位置(使用者定義)
		//但是現在不會走這條路,因為配置檔案還沒有定位到,也就是沒有封裝成Resource物件。
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);//載入resources
		}
		//以String的方式獲取所有配置檔案的位置(容器自身)
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			reader.loadBeanDefinitions(configLocations);//載入resources
		}
	}

這裡主要是獲取到使用者定義的resource資源位置以及獲取所以本地配置檔案的位置。


⑥、進入第二個reader.loadBeanDefinitions(configLocations)方法。從這裡開始就是BeanDefinitionReader模組的實現了,也就是ApplicationContext上下文將BeanDefinition的定位載入工作交付到了XmlBeanDefinitionReader。這個方法是由XmlBeanDefinitionReader的基類AbstractBeanDefinitionReader來實現的。

	public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
		Assert.notNull(locations, "Location array must not be null");
		int count = 0;
		//循壞載入配置檔案
		for (String location : locations) {
			count += loadBeanDefinitions(location);
		}
		return count;
	}

這裡就是迴圈載入xml配置檔案的路徑,然後返回總個數。


⑦、下面我們繼續跟蹤loadBeanDefinitions(loaction)這個方法,它是還在AbstractBeanDefinitionReader的類中實現。

public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(location, null);
	}


⑧、繼續跟蹤上面程式碼中的 loadBeanDefinitions(location, null)。

進入到loadBeanDefinitions(String location, Set<Resource> actualResources)這個方法,依然在AbstractBeanDefinitionReader類中。

	public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
		//這裡取到ResourceLoader物件(其實DefaultResourceLoader物件)
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
		}
		//這裡對Resource的路徑模式進行解析,比如我們設定的各種Ant格式的路徑定義,得到需要的Resource集合,
		//這些Resource集合指定我們已經定義好的BeanDefinition資訊,可以是多個檔案。
		if (resourceLoader instanceof ResourcePatternResolver) {
			try {
				//把字串型別的xml檔案路徑,形如:classpath*:user/**/*-context.xml,轉換成Resource物件型別,
				//其實就是用流的方式載入配置檔案,然後封裝成Resource物件
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				//載入Resource資源中的Bean,然後返回載入數量,這個loadBeanDefinitions就是Bean的載入了
				int count = loadBeanDefinitions(resources);
				if (actualResources != null) {
					Collections.addAll(actualResources, resources);
				}
				if (logger.isTraceEnabled()) {
					logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
				}
				return count;
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		else {
			// Can only load single resources by absolute URL.
			// 呼叫DefaultResourceLoader的getResource(String)方法來獲取資源定位,然後封裝成Resource物件,這裡只能載入一個資源
			Resource resource = resourceLoader.getResource(location);
			//迴圈載入所有的資源,返回總數,這個loadBeanDefinitions就是Bean的載入了
			int count = loadBeanDefinitions(resource);
			if (actualResources != null) {
				//對於成功找到的Resource定位,都會新增到這個傳入的actualResources引數中
				actualResources.add(resource);
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
			}
			return count;
		}
	}

這個方法中主要將xml配置檔案載入到記憶體中並封裝成為Resource物件。但是它是怎麼操作的呢?在上述程式碼中,loadBeanDefinitions()方法中可能呼叫ResourcePatternResolver或DefaultResourceLoader中的getResource()方法,這兩個類一個是繼承、一個是實現ResourceLoader。其中ResourcePatternResolver用於解析資原始檔的策略介面,其特殊的地方在於,它應該提供帶有*號這種萬用字元的資源路徑。DefaultResourceLoader用於用來載入資源,並且具體實現了ResourceLoader中的方法。而在第④步的時候,在例項化XmlBeanDefinitionReader的時候已經設定ResourceLoader,並且ResourceLoad為ApplicationContext,然後也設定了ResourcePatternResolver。所以XmlBeanDefinitionReader有了載入資源和解析資源的功能。


⑨、所以我們直接來看getResource()方法,DefaultResourceLoader中的 getResource(String)實現。

	public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");
		//看有沒有自定義的ProtocolResolver,如果有則先根據自定義的ProtocolResolver解析location得到Resource
		for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
			Resource resource = protocolResolver.resolve(location, this);
			if (resource != null) {
				return resource;
			}
		}
		//根據路徑是否匹配"/"或"classpath:"來解析得到ClassPathResource
		if (location.startsWith("/")) {
			return getResourceByPath(location);
		}
		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		else {
			try {
				//這裡處理帶有URL標識的Resource定位
				URL url = new URL(location);
				return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
			}
			catch (MalformedURLException ex) {
				//如果既不是classPath 也不是URL標識的Resource定位(那其實就是自己實現的了).則把getResource的重任交給getResourceByPath來完成,
				//這個方法是一個protected方法,預設的實現是得到一個ClassPathContextResource,這個方法常常會用子類來實現也就是FileSystemXMLApplicationContext
				return getResourceByPath(location);
			}
		}
	}

通過上述程式碼可以看到,getResource最後又呼叫了子類實現的getResourceByPath方法或是子類傳遞過來的字串,從而實現Resource定位。使得整個Resource定位過程就說得通了。總結起來就是,Resource資源通過最外層的實現類傳進來的字串或者直接呼叫getResourceByPath方法,來獲取bean資源路徑。

對上面的程式碼進行四步來進行介紹:

  • 第一步:首先看有沒有自定義的ProtocolResolver,如果有則先根據自定義的ProtocolResolver解析location得到Resource(預設ProtocolResolver是空的,後面我們會說)
  • 		for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
    			Resource resource = protocolResolver.resolve(location, this);
    			if (resource != null) {
    				return resource;
    			}
    		}

    這裡的protocolResolvers是DefaultResourceLoader類中的成員變數,而這個成員變數是ProtocolResolver型別的Set集合。

    • 第二步:再根據路徑是否匹配"/"或"classpath:"來解析得到ClassPathResource。
    • 		if (location.startsWith("/")) {
      			return getResourceByPath(location);
      		}
      		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
      			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
      		}
    • 第三步:最後處理帶有URL標識的Resource定位,載入得到一個UrlResource,如果都不是這些型別,則交給getResourceByPath來完成。
    • 		else {
      			try {
      				// Try to parse the location as a URL...
      				URL url = new URL(location);
      				return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
      			}
      			catch (MalformedURLException ex) {
      				// No URL -> resolve as resource path.
      				return getResourceByPath(location);
      			}
      		}
    • 第四步:上面的getResourceByPath()方法會根據路徑載入Resource物件
    • 	protected Resource getResourceByPath(String path) {
      		return new ClassPathContextResource(path, getClassLoader());
      	}

    上面方法返回的是一個ClassPathContextResource物件,通過這個物件Spring就可以進行相關的I/O操作了。


    因為對ProtocolResolver這個類不是很熟悉,所以我去了解了一下,ProtocolResolver翻譯過來就是"協議解析器",這個介面類裡就只有一個方法,方法如下:

    Resource resolve(String location, ResourceLoader resourceLoader);
    

    我們在第一步的時候呼叫了ProtocolResolver的resolve方法,如果你要使用ProtocolResolver。我們可以自定義一個類實現ProtocolResolver介面,然後實現該resolve方法,就可以解析特定的location得到Resoure。是的,ProtocolResolver是解析location的自定義拓展類,有了它我們才能隨意傳入不同格式的location,然後根據對應的格式去解析並獲得我們的Resource即可。


    關於DefaultResourceLoader和ProtocolResolver的區別:

    1. DefaultResourceLoader類的作用是載入Resource
    2. ProtocolResolver是解析location獲取Resource的拓展

    預設情況下,DefaultResourceLoader類中的protocolResolvers成員變數是一個空的Set,即預設情況下是沒有ProtocolResolver可以去解析的,只能走ClassPath和URL兩種方式獲得Resource。


    至此我們的Resource定位已經全部完成了。饒了這麼遠就是為了拿到這個Resource物件,拿到這個物件後,就可以通過AbstractBeanDefinitionReader流操作來實現Resource的載入,最後通過AbstractApplicationContext的registerListeners來進行註冊。這就是IoC容器的初始化過程。所以下面我們來介紹一下Resource的載入工程。

    4、BeanDefinition的載入

    在完成對Resource定位分析之後,就可以通過獲取的Resource物件進行BeanDefinition的載入了。對IOC容器來說,這個載入過程,相當於把定義的bean在IOC容器中轉化成一個Spring內部表示的資料結構的過程,也就是將其轉化為BeanDefinition,IOC容器對Bean的管理和依賴注入功能的實現,是通過對其持有的BeanDefinition進行各種相關操作來完成的,這些BeanDefinition在IOC容器中通過一個HashMap來保持和維護。

    我們繼續跟蹤AbstractBeanDefinitionReader中的loadBeanDefinitions方法,之前跟蹤到的是如下圖的loadBeanDefinitions方法。


    ①、繼續跟到loadBeanDefinitions(resource)方法。

    public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
         Assert.notNull(resources, "Resource array must not be null");
         int count = 0;
         // 將所有定位到的Resource資源全部載入,交給XmlBeanDefinitionReader實現的方法來處理這些resource
         for (Resource resource : resources) {
             count += loadBeanDefinitions(resource);
         }
         return count;
     }

    這裡迴圈載入定位到Resource資源,這個方法跟前面迴圈載入資源路徑類似,但載入的內容不一樣。


    ②、然後點選進入loadBeanDefinitions(resource),進入之後我們可以發現,在BeanDefinitionReader介面定義了兩個載入Resource資源的方法:

    int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
    
    int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;

    這兩個方法具體由BeanDefinitionReader介面的子類XmlBeanDefinitionReader 實現,其繼承關係如下圖所示。

    XmlBeanDefinitionReader主要用來將Bean的XML配置檔案轉換為多個BeanDefinition物件的工具類,所以它會將定位到的Resource資源進行處理。我們先來看上面兩個實現的方法,大致過程是,先將resource包裝為EncodeResource型別,然後繼續進行處理,為生成BeanDefinition物件為後面做準備,我們在XmlBeanDefinitionReader類中找到實現的方法,其主要的兩個方法的原始碼如下。

    	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    		//包裝resource為EncodeResource型別
    		return loadBeanDefinitions(new EncodedResource(resource));
    	}
    	
    	public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    		Assert.notNull(encodedResource, "EncodedResource must not be null");
    		if (logger.isTraceEnabled()) {
    			logger.trace("Loading XML bean definitions from " + encodedResource);
    		}
    		// 這裡使用threadLocal來保證併發的同步
    		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    		//先新增threadLocal,載入完之後finally中再移除threadLocal
    		if (!currentResources.add(encodedResource)) {
    			throw new BeanDefinitionStoreException(
    					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    		}
    		// 通過resource物件得到XML檔案內容輸入流,併為I/O的InputSource做準備
    		try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
    			InputSource inputSource = new InputSource(inputStream);
    			if (encodedResource.getEncoding() != null) {
    				inputSource.setEncoding(encodedResource.getEncoding());
    			}
    			//這裡就是具體讀取Xml檔案的方法
    			return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    		}
    		catch (IOException ex) {
    			throw new BeanDefinitionStoreException(
    					"IOException parsing XML document from " + encodedResource.getResource(), ex);
    		}
    		finally {
    			currentResources.remove(encodedResource);
    			if (currentResources.isEmpty()) {
    				this.resourcesCurrentlyBeingLoaded.remove();
    			}
    		}
    	}


    ③、接著進入doLoadBeanDefinitions方法,這裡就是具體讀取Xml檔案的方法,也是從指定xml檔案中實際載入BeanDefinition的地方。當然了這肯定是在XmlBeanDefinitionReader中的方法了。

    	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
    			throws BeanDefinitionStoreException {
    
    		try {
    			//這裡取得的是XML檔案的Document物件,具體的解析過程是由DocumentLoader完成的,
    			//這裡使用的DocumentLoader是DefaultDocumentLoader,在定義documentLoader物件時候建立的
    			Document doc = doLoadDocument(inputSource, resource);
    			//這裡啟動的是對BeanDefinition解析的詳細過程,也就是將document檔案的bean封裝成BeanDefinition,並註冊到容器
    			//啟動對BeanDefinition解析的詳細過程,這個解析會用到Spring的Bean配置規則,是我們下面詳細講解的內容
    			int count = registerBeanDefinitions(doc, resource);
    			if (logger.isDebugEnabled()) {
    				logger.debug("Loaded " + count + " bean definitions from " + resource);
    			}
    			return count;
    		}
    		catch () {
    			省略......
    		}
    	}

    DefaultDocumentLoader這個類大致瞭解即可,感興趣可自行百度。


    ④、下面我們主要關心的是Spring的BeanDefinition是怎麼樣按照Spring的Bean語義要求進行解析 並轉化為容器內部資料結構的,這個過程是在registerBeanDefinitions(doc, resource)中完成的,具體的過程是BeanDefinitionDocumentReader來完成的,這個registerBeanDefinitions還對載入的Bean數量進行了統計,這個方法也是在 XmlBeanDefinitionReader 中自己實現的,

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
         //這裡得到的BeanDefinitionDocumentReader物件來對XML的BeanDefinition資訊進行解析
         BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
         //獲取容器中bean的數量
         int countBefore = getRegistry().getBeanDefinitionCount();
         //具體的解析過程在這個方法中實現    
         documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
         return getRegistry().getBeanDefinitionCount() - countBefore;
     }

    注意:BeanDefinition的載入分成兩部分,首先通過呼叫XML的解析器(XmlBeanDefinitionReader)得到document物件,但這些document物件並沒有 按照Spring的Bean規則去進行解析,在完成通用XML解析之後才是按照Spring得 Bean規則進行解析的地方,這個按照Spring的Bean規則進行解析的過程是在documentReade中實現的,這裡使用的documentReader是預設設定好的DefaultBeanDefinitionDocumentReader,建立的過程也是在XmlBeanDefinitionReader 中完成的,根據指定的預設方式如下:

    private Class<? extends BeanDefinitionDocumentReader> documentReaderClass =
          DefaultBeanDefinitionDocumentReader.class;
    protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
       return BeanUtils.instantiateClass(this.documentReaderClass);
    }
    

    上面通過通過 XmlBeanDefinitionReader 類中的私有屬性 documentReaderClass 獲得一個 DefaultBeanDefinitionDocumentReader 例項物件,並且具體的解析過程在DefaultBeanDefinitionDocumentReader來實現,所以下面我們繼續跟蹤。


    ⑤、DefaultBeanDefinitionDocumentReader實現了BeanDefinitionDocumentReader介面,它的registerBeanDefinitions方法定義如下:

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
       this.readerContext = readerContext;
       doRegisterBeanDefinitions(doc.getDocumentElement());
    }

    這裡只是將 XML中的元素取了出來,但是具體的活還是 doRegisterBeanDefinitions(root)來實現的,do開頭的方法才是真正幹活的方法。


    ⑥、所以繼續跟蹤doRegisterBeanDefinitions(root)方法

    protected void doRegisterBeanDefinitions(Element root) {
         // 建立了BeanDefinitionParserDelegate物件
         BeanDefinitionParserDelegate parent = this.delegate;
         this.delegate = createDelegate(getReaderContext(), root, parent);
    
        // 如果是Spring原生名稱空間,首先解析 profile標籤,這裡不重要
         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)) {
                     if (logger.isDebugEnabled()) {
                         logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                 "] not matching: " + getReaderContext().getResource());
                     }
                     return;
                 }
             }
         }
         //解析BeanDefinition之前做的一些事情的介面觸發
         preProcessXml(root);
         //主要看這個方法,標籤具體解析過程
         parseBeanDefinitions(root, this.delegate);
         // 解析BeanDefinition之後可以做的一些事情的觸發
         postProcessXml(root);
    
        this.delegate = parent;
     }

    在這個方法中,我們重點看“一類三法”,也就是BeanDefinitionParserDelegate類和preProcessXml、parseBeanDefinitions、postProcessXml三個方法。其中BeanDefinitionParserDelegate類非常非常重要(需要了解代理技術,如JDK動態代理、cglib動態代理等)。Spirng BeanDefinition的解析就是在這個代理類下完成的,此類包含了各種對符合Spring Bean語義規則的處理,比如<bean></bean>、<import></import>、<alias><alias/>等的檢測。對於preProcessXml、parseBeanDefinitions、postProcessXml這三個方法,其中preProcessXml和postProcessXml都是空方法,意思是在解析標籤前後我們自己可以擴充套件需要執行的操作,也是一個模板方法模式,體現了Spring的高擴充套件性。parseBeanDefinitions方法才是標籤的具體解析過程。所以下面進入parseBeanDefinitions方法看具體是怎麼解析標籤的。


    ⑦、前面提到Document物件不能通過XmlBeanDefinitionReader,真正去解析Document文件樹的是 BeanDefinitionParserDelegate完成的,這個解析過程是與Spring對BeanDefinition的配置規則緊密相關的,parseBeanDefinitions(root, delegate)方法如下:

    	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    		if (delegate.isDefaultNamespace(root)) {
    			NodeList nl = root.getChildNodes();
    			// 遍歷所有節點,做對應解析工作
                // 如遍歷到<import>標籤節點就呼叫importBeanDefinitionResource(ele)方法對應處理
                // 遍歷到<bean>標籤就呼叫processBeanDefinition(ele,delegate)方法對應處理
    			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);
    		}
    	}
    

    這裡有兩種標籤的解析:Spring原生標籤和自定義標籤,那來怎麼區分這兩種標籤呢?如下:

    • 預設標籤:<bean:/>
    • 自定義標籤:<context:component-scan/>

    如果帶有bean的就是Spring預設標籤,否則就是自定義標籤。但無論哪種標籤在使用前都需要在Spring的xml配置檔案裡宣告Namespace URI,這樣在解析標籤時才能通過Namespace URI找到對應的NamespaceHandler。引入:xmlns:context=http://www.springframework.org/schema/contex http://www.springframework.org/schema/beans


    ⑧、上面的程式碼中先是isDefaultNamespace判斷是不是預設標籤,然後進入parseDefaultElement方法(自定義方法感興趣可以自行百度):

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        // 解析<import>標籤
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            importBeanDefinitionResource(ele);
        }
        // 解析<alias>標籤
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            processAliasRegistration(ele);
        }
        // 解析<bean>標籤,最常用,過程最複雜
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            processBeanDefinition(ele, delegate);
        }
        // 解析<beans>標籤
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            doRegisterBeanDefinitions(ele);
         }
     }

    這裡面主要是對import、alias、bean標籤的解析以及beans的字標籤的遞迴解析。


    ⑨、這裡針對常用的<bean>標籤中的方法做簡單介紹,其他標籤的載入方式類似,進入processBeanDefinition方法。

    	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    		//BeandefinitionHolder是BeanDefinition的封裝,封裝了BeanDefinition,bean的名字和別名,用它來完成向IOC容器註冊,
    		//得到BeanDefinitionHodler就意味著BeanDefinition是通過BeanDefinitionParseDelegate對xml元素按照bean的規則解析得到的
    		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    		if (bdHolder != null) {
    			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
    			try {
    				// 這裡是向IOC容器解析註冊得到BeanDefinition的地方
    				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
    			}
    			catch (BeanDefinitionStoreException ex) {
    				getReaderContext().error("Failed to register bean definition with name '" +
    						bdHolder.getBeanName() + "'", ele, ex);
    			}
    			// 在BeanDefinition向Ioc容器註冊完成後傳送訊息
    			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    		}
    	}


    ⑩、進入parseBeanDefinitionElement(Element ele)方法方法。

    注意:parseBeanDefinitionElement(Element ele)方法會呼叫parseBeanDefinitionElement(ele, null)方法,需要強調一下的是parseBeanDefinitionElement(ele, null)方法中產生了一個抽象型別的BeanDefinition例項,這也是我們首次看到直接定義BeanDefinition的地方,這個方法裡面會將<bean>標籤中的內容解析到BeanDefinition中,如果在解析<bean>標籤的過程中出現錯誤則返回null,之後再對BeanDefinition進行包裝,將它與beanName,Alias等封裝到BeanDefinitionHolder 物件中,然後返回BeanDefinitionHolder類物件,該部分原始碼如下:

    	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
    		return parseBeanDefinitionElement(ele, null);
    	}
    	
    	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    		// 獲取id和name屬性
    		String id = ele.getAttribute(ID_ATTRIBUTE);
    		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 (logger.isTraceEnabled()) {
    				logger.trace("No XML 'id' specified - using '" + beanName +
    						"' as bean name and " + aliases + " as aliases");
    			}
    		}
    		// 檢查beanName是否重複
    		if (containingBean == null) {
    			checkNameUniqueness(beanName, aliases, ele);
    		}
    		// 具體的解析封裝過程還在這個方法裡
    		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    		if (beanDefinition != null) {
    			if (!StringUtils.hasText(beanName)) {
    				try {
    					if (containingBean != null) {
    						beanName = BeanDefinitionReaderUtils.generateBeanName(
    								beanDefinition, this.readerContext.getRegistry(), true);
    					}
    					else {
    						beanName = this.readerContext.generateBeanName(beanDefinition);
    						// Register an alias for the plain bean class name, if still possible,
    						// if the generator returned the class name plus a suffix.
    						// This is expected for Spring 1.2/2.0 backwards compatibility.
    						String beanClassName = beanDefinition.getBeanClassName();
    						if (beanClassName != null &&
    								beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
    								!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
    							aliases.add(beanClassName);
    						}
    					}
    					if (logger.isTraceEnabled()) {
    						logger.trace("Neither XML 'id' nor 'name' specified - " +
    								"using generated bean name [" + beanName + "]");
    					}
    				}
    				catch (Exception ex) {
    					error(ex.getMessage(), ele);
    					return null;
    				}
    			}
    			String[] aliasesArray = StringUtils.toStringArray(aliases);
    			return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    		}
    
    		return null;
    	}

    上面的解析過程可以看做根據xml檔案對<bean>的定義生成BeanDefinition物件的過程,這個BeanDefinition物件中封裝的資料大多都是與<bean>相關的,例如:init-method,destory-method,factory-method,beanClass,descriptor。有了這個BeanDefinition中分裝的資訊,容器才能對Bean配置進行處理以及實現容器的特性。至此,我們的BeanDefine就已經載入完成了。


    ⑪、下面再來多加一個點,看一下bean的具體解析。

    	public AbstractBeanDefinition parseBeanDefinitionElement(
    			Element ele, String beanName, @Nullable BeanDefinition containingBean) {
    
    		this.parseState.push(new BeanEntry(beanName));
    		// 獲取class名稱和父類名稱
    		String className = null;
    		if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
    			className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    		}
    		// 解析 parent 屬性
    		String parent = null;
    		if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
    			parent = ele.getAttribute(PARENT_ATTRIBUTE);
    		}
    
    		try {
    			// 建立GenericBeanDefinition物件
    			AbstractBeanDefinition bd = createBeanDefinition(className, parent);
    			// 解析bean標籤的屬性,並把解析出來的屬性設定到BeanDefinition物件中
    			parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
    			bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
    			//解析bean中的meta標籤
    			parseMetaElements(ele, bd);
    			//解析bean中的lookup-method標籤
    			parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
    			//解析bean中的replaced-method標籤 
    			parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
    			//解析bean中的constructor-arg標籤
    			parseConstructorArgElements(ele, bd);
    			//解析bean中的property標籤 
    			parsePropertyElements(ele, bd);
    			// 解析子元素 qualifier 子元素
    			parseQualifierElements(ele, bd);
    
    			bd.setResource(this.readerContext.getResource());
    			bd.setSource(extractSource(ele));
    
    			return bd;
    		}
    		catch (ClassNotFoundException ex) {
    			error("Bean class [" + className + "] not found", ele, ex);
    		}
    		catch (NoClassDefFoundError err) {
    			error("Class that bean class [" + className + "] depends on not found", ele, err);
    		}
    		catch (Throwable ex) {
    			error("Unexpected failure during bean definition parsing", ele, ex);
    		}
    		finally {
    			this.parseState.pop();
    		}
    
    		return null;
    	}

    上面的程式碼是具體生成BeanDefinition的地方,bean標籤的解析步驟仔細理解並不複雜,就是將一個個標籤屬性的值裝入到了BeanDefinition物件中,這裡需要注意parseConstructorArgElements和parsePropertyElements方法,分別是對constructor-arg和property標籤的解析,解析完成後分別裝入了BeanDefinition物件的constructorArgumentValues和propertyValues中,而這兩個屬性在c和p標籤的解析中還會用到,而且還涉及一個很重要的設計思想——裝飾器模式。Bean標籤解析完成後將生成的BeanDefinition物件、bean的名稱以及別名一起封裝到了BeanDefinitionHolder物件並返回,然後呼叫了decorateBeanDefinitionIfRequired進行裝飾,後面具體的呼叫就不具體介紹了,想了解的可以自行百度。

    5、BeanDefinition的註冊

    在完成了BeanDefinition的載入和解析後,就要對它進行註冊。我們知道最終Bean配置會被解析成BeanDefinition並與beanName,Alias一同封裝到BeanDefinitionHolder類中,然後返回這個物件,所以我們順著BeanDefinitionHolder類建立的地方,也就是DefaultBeanDefinitionDocumentReader的processBeanDefinition()方法繼續往下看。

    	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    		//BeandefinitionHolder是BeanDefinition的封裝,封裝了BeanDefinition,bean的名字和別名,用它來完成向IOC容器註冊,
    		//得到BeanDefinitionHodler就意味著BeanDefinition是通過BeanDefinitionParseDelegate對xml元素按照bean的規則解析得到的
    		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    		if (bdHolder != null) {
    			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
    			try {
    				// 這裡是向IOC容器解析註冊得到BeanDefinition的地方
    				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
    			}
    			catch (BeanDefinitionStoreException ex) {
    				getReaderContext().error("Failed to register bean definition with name '" +
    						bdHolder.getBeanName() + "'", ele, ex);
    			}
    			// 在BeanDefinition向Ioc容器註冊完成後傳送訊息
    			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    		}
    	}


    然後跟蹤到BeanDefinitionReaderUtils的registerBeanDefinition()方法,這裡會傳入上一步的BeanDefinitionHolder物件,並且將BeanDefinition註冊到IoC容器中。進入BeanDefinitionReaderUtils類的registerBeanDefinition方法如下。

    	public static void registerBeanDefinition(
    			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
    			throws BeanDefinitionStoreException {
    
    		// 註冊beanDefinition!!
    		String beanName = definitionHolder.getBeanName();
    		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    
    		// 如果有別名的話也註冊進去
    		String[] aliases = definitionHolder.getAliases();
    		if (aliases != null) {
    			for (String alias : aliases) {
    				registry.registerAlias(beanName, alias);
    			}
    		}
    	}


    之後會呼叫BeanDefinitionRegistry介面的registerBeanDefinition( beanName, bdHolder.getBeanDefinition())方法,而對於IoC容器中最重要的一個類DefaultListableBeanFactory實現了該介面的方法。這個方法的主要目的就是將BeanDefinition存放至DefaultListableBeanFactory物件的beanDefinitionMap中,當初始化容器進行bean初始化時,在bean的生命週期分析裡必然會在這個beanDefinitionMap中獲取beanDefition例項。我們可以在DefaultListableBeanFactory中看到此Map的定義。

    /** Map of bean definition objects, keyed by bean name. */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);


    下面我們在來看一下這個方法是如將BeanDefinition存放至beanDefinitionMap中的,DefaultListableBeanFactory中實現的registerBeanDefinition( beanName, bdHolder.getBeanDefinition() )方法具體如下:

    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
    			throws BeanDefinitionStoreException {
    
    		Assert.hasText(beanName, "Bean name must not be empty");
    		Assert.notNull(beanDefinition, "BeanDefinition must not be null");
    
    		if (beanDefinition instanceof AbstractBeanDefinition) {
    			try {
    				((AbstractBeanDefinition) beanDefinition).validate();
    			}
    			catch (BeanDefinitionValidationException ex) {
    				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
    						"Validation of bean definition failed", ex);
    			}
    		}
    
    		//此處檢查是不是有相同名字的Bean存在
    		//如果名字相同又不允許覆蓋,就會丟擲異常BeanDefinitionOverrideException
    		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    		if (existingDefinition != null) {
    			if (!isAllowBeanDefinitionOverriding()) {
    				throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
    			}
    			else if (existingDefinition.getRole() < beanDefinition.getRole()) {
    				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
    				if (logger.isInfoEnabled()) {
    					logger.info("Overriding user-defined bean definition for bean '" + beanName +
    							"' with a framework-generated bean definition: replacing [" +
    							existingDefinition + "] with [" + beanDefinition + "]");
    				}
    			}
    			else if (!beanDefinition.equals(existingDefinition)) {
    				if (logger.isDebugEnabled()) {
    					logger.debug("Overriding bean definition for bean '" + beanName +
    							"' with a different definition: replacing [" + existingDefinition +
    							"] with [" + beanDefinition + "]");
    				}
    			}
    			else {
    				if (logger.isTraceEnabled()) {
    					logger.trace("Overriding bean definition for bean '" + beanName +
    							"' with an equivalent definition: replacing [" + existingDefinition +
    							"] with [" + beanDefinition + "]");
    				}
    			}
    			//儲存Bean(Bean名字作為key,BeanDefinition作為value)
    			this.beanDefinitionMap.put(beanName, beanDefinition);
    		}
    		else {
    			if (hasBeanCreationStarted()) {
    				//註冊的過程需要保證資料的一致性
    				synchronized (this.beanDefinitionMap) {
    					//將獲取到的BeanDefinition放入Map中,容器操作使用bean時通過這個HashMap找到具體的BeanDefinition
    					//儲存Bean(Bean名字作為key,BeanDefinition作為value)
    					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;
    		}
    
    		if (existingDefinition != null || containsSingleton(beanName)) {
    			resetBeanDefinition(beanName);
    		}
    		else if (isConfigurationFrozen()) {
    			clearByTypeCache();
    		}
    	}

    當把所有的BeanDefinition(懶載入除外)都存入IOC容器中的HashMap後,註冊就結束了。但是注意,以上僅僅是BeanDefinition的載入、載入和註冊,Bean之間的依賴關係並不會在初始化的時候完成!後面還需要呼叫一系列方法才會完成初始化。

    這篇文章算是我自己比較深入瞭解Spring了吧,我也沒怎麼看過Spring的原始碼,所以參考了很多網上部落格才寫出來,之所以還是要寫下這篇部落格,是因為想要更加深入的瞭解一下Spring,當然這只是它的一點皮毛。我也希望在後面的學習中不斷提高自己的技術,同時記錄自己學習過程中的點點滴滴,所以部落格中肯定有許多不足之處,望諒解與指出,歡迎大家評論指出。

    參考資料:

    1. 《JavaEE入門實戰》
    2. https://www.cnblogs.com/ChenD/p/10235579.html
    3. https://blog.csdn.net/sinat_34596644/article/details/80394209
    4. https://www.cnblogs.com/chenjunjie12321/p/6124649.html
    5. https://www.cnblogs.com/yewy/p/13111818.html
    6. https://www.cnblogs.com/pepper7/p/7671038.html