Spring原始碼分析:Spring IOC容器初始化

JayceKon發表於2018-04-14

概述

Spring 對於Java 開發來說,以及算得上非常基礎並且核心的框架了,在有一定開發經驗後,閱讀原始碼能更好的提高我們的編碼能力並且讓我們對其更加理解。俗話說知己知彼,百戰不殆。當你對Spring 掌握熟透之後,也就沒什麼能過阻攔你在開發路上前進了。

IOC 總體來說有兩處地方最重要,一個是建立Bean容器,一個是初始化。在本文中,主要為大家講解了 IOC Bean 容器建立過程。後續將會補上初始化部分的知識。

為了保持文章的嚴謹性,如果讀者發現我哪裡說錯了請一定不吝指出,非常希望可以聽到讀者的聲音。同時能過糾正自己的誤解。

本文主要採用了ClassPathXmlApplication作為Spring IOC 容器,從 ClassPathXmlApplication 分析容器建立的過程,首先來看一下ClassPathXmlApplication 的依賴關係:

Spring原始碼分析:Spring IOC容器初始化

首先我們先來觀察一下核心主幹:

Spring原始碼分析:Spring IOC容器初始化

我們可以通過對ClassPathXmlApplicationContext 的父類名稱,瞭解其主要功能:

  • DefaultResourceLoader:提供獲取配置檔案方法 getResource(),返回資源資訊 Resource
  • AbstractApplicationContext:提供主要建立容器,初始化物件方法 refresh()
  • AbstractRefreshableApplicationContext: 提供重新整理 refreshBeanFactory() 方法,進行BeanFactory 的初始化。
  • AbstractRefreshableConfigApplicationContext:儲存配置檔案資訊。
  • AbstractXmlApplicationContext:提供 loadBeanDefinitions() 讀取BeanDefinitions方法,將資源轉換為配置。
  • ClassPathXmlApplicationContext:具體實現類,用於定位資原始檔。

通過上述主要類的分析,相信大家對Spring IOC 的初始化有了大概的認識。

簡單來說,Spring IOC 容器建立的具體流程如下:

Spring原始碼分析:Spring IOC容器初始化

我們將圖片對應到類的呼叫,具體步驟為:

1、ClassPathXmlApplicationContext -> 通過構造器,讀取XML配置檔案地址資訊

2、AbstractApplicationContext -> refresh() 初始化容器

3、AbstractRefreshableApplicationContext -> BeanFactory 初始化

4、AbstractXmlApplicationContext -> loadBeanDifinition 讀取資源資訊(載入Bean)

5、XmlBeanDefinitionReader -> 將資原始檔解析成 BeanDefinition

6、DefaultBeanDefinitionDocumentReader -> 將BeanDefinition 註冊到BeanFactory 中

由於Spring IOC部分原始碼主要包括了:容器建立以及Bean 初始化兩部分。在本文內容中,主要介紹一下,容器建立的過程!

1、容器例項

ApplicationContext.xml 配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="User" class="com.charles.business.model.User">
        <property name="username" value="jaycekon"/>
        <property name="phone" value="1881412***"/>
    </bean>
</beans>
複製程式碼

程式碼啟動入口:

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        User user = (User) applicationContext.getBean("User");
        Assert.notNull(user, "容器初始化異常!");
        logger.info("初始化結果:{}", JSONObject.toJSONString(user));
    }
複製程式碼

接下來我們根據ClassPathXmlApplication 的依賴圖進行逐一分析每一個過程。

2、ClassPathXmlApplicationContext

核心方法:

org.springframework.context.support.ClassPathXmlApplicationContext#ClassPathXmlApplicationContext(java.lang.String[], boolean, org.springframework.context.ApplicationContext)
複製程式碼

Spring原始碼分析:Spring IOC容器初始化

從上圖可以看出,ClassPathXmlApplicationContext 類中並沒有提供什麼特別的方法,除構造器外,只有一個獲取Resource 的方法(與之相似的 AbstractRefreshableConfigApplicationContext 中提供類獲取資源定位的方法),主要用於儲存資源資訊。

ClassPathXmlApplicationContext 中的主要構造方法:

	public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {
	// 呼叫父類構造方法
		super(parent);
	// AbstractRefreshableConfigApplicationContext 具體實現,儲存資源定位資訊
		setConfigLocations(configLocations);
		if (refresh) {
       //  AbstractApplicationContext 具體實現,初始化容器與Bean
			refresh();
		}
	}
複製程式碼

可以看到,ClassPathXmlApplicationContext 這個類相當簡單,主要作用:

  • Resource[] configResources:將配置檔案作為資源都存放到這個陣列中
  • setConfigLocations():儲存配置檔案定位資訊(AbstractRefreshableConfigApplicationContext)
  • refresh():啟動容器初始化核心方法(AbstractApplicationContext)

3、AbstractApplicationContext

核心方法:

org.springframework.context.support.AbstractApplicationContext#refresh
複製程式碼

AbstractApplicationContext 是Spring IOC 容器中核心類,對父類,介面提供了具體實現,其中的方法非常豐富,在這裡我們主要介紹其中的核心方法 refresh()。 這裡簡單說下為什麼是 refresh(),而不是 init() 這種名字的方法。因為 ApplicationContext 建立起來以後,其實我們是可以通過呼叫 refresh() 這個方法重建的,這樣會將原來的 ApplicationContext 銷燬,然後再重新執行一次初始化操作。

public void refresh() throws BeansException, IllegalStateException {
		//對容器初始化進行加鎖操作,比免在建立的同時,重複操作。
		synchronized (this.startupShutdownMonitor) {
			// 設定容器初始化世界,表示容器現在isActive,初始化上下文環境中的任何佔位符屬性源
			prepareRefresh();

			// 核心步驟,主要功能是:讓子類進行BeanFactory 初始化,並且將Bean資訊 轉換為BeanFinition,最後註冊到容器中
			// 當然,這裡說的 Bean 還沒有初始化,只是配置資訊都提取出來了
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// 設定 BeanFactory 的類載入器,新增幾個 BeanPostProcessor,手動註冊幾個特殊的 bean
			prepareBeanFactory(beanFactory);

			try {
			    // 後續步驟將在下次進行分析,本文主要核心為上面幾個步驟
			    ...      
			}
			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}
複製程式碼

從AbstractApplicationContext 中的refresh()我們可以粗略的領會到,Spring IOC 的核心流程,基本在這裡完成的,從容器建立,資源解析,Bean建立等一系列步驟。都是由這個方法進行控制。

在本文中,我們主要關心的一個點就是:

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
複製程式碼

Spring原始碼分析:Spring IOC容器初始化

通過上圖,我們可以直觀的瞭解到ApplicationContext 與BeanFactory 之間的關係。這裡建立的 ConfigurableListableBeanFactory 實際包含了BeanFactory 三個分支的絕大部分功能。

1、ApplicationContext 繼承了 ListableBeanFactory,這個 Listable 的意思就是,通過這個介面,我們可以獲取多個 Bean,最頂層 BeanFactory 介面的方法都是獲取單個 Bean 的。

2、ApplicationContext 繼承了 HierarchicalBeanFactory,Hierarchical 單詞本身已經能說明問題了,也就是說我們可以在應用中起多個 BeanFactory,然後可以將各個 BeanFactory 設定為父子關係。

3、AutowireCapableBeanFactory 這個名字中的 Autowire 大家都非常熟悉,它就是用來自動裝配 Bean 用的,但是仔細看上圖,ApplicationContext 並沒有繼承它,不過不用擔心,不使用繼承,不代表不可以使用組合,如果你看到 ApplicationContext 介面定義中的最後一個方法 getAutowireCapableBeanFactory() 就知道了。

4、ConfigurableListableBeanFactory 也是一個特殊的介面,看圖,特殊之處在於它繼承了第二層所有的三個介面,而 ApplicationContext 沒有。這點之後會用到。

我們來看一下 obtainFreshBeanFactory() 方法的主要內容:

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		//AbstractRefreshableApplicationContext 中實現,主要用於重新整理BeanFactory,載入 Bean 定義、註冊 Bean 等等
		refreshBeanFactory();
		//獲取上述步驟中 建立好的 BeanFactory
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (logger.isDebugEnabled()) {
			logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
		}
		return beanFactory;
	}
複製程式碼

4、AbstractRefreshableApplicationContext

核心方法:

org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory
複製程式碼

其實在閱讀原始碼的過程中,讀懂類的命名能過為我們提供很大的幫助,例如 AbstractRefreshableApplicationContext 這個類,可以看出她繼承了AbstractApplicationContext 並且主要實現了其中的refresh 功能,當然,這裡的refresh 並不是指 refresh() 方法。而是指的:refreshBeanFactory()

	@Override
	protected final void refreshBeanFactory() throws BeansException {
		//判斷當前ApplicationContext是否已經有 BeanFactory ,如果有,銷燬所有 Bean,關閉 BeanFactory
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
		    // 初始化一個 DefaultListableBeanFactory
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			
			// 設定 BeanFactory 的兩個配置屬性:是否允許 Bean 覆蓋、是否允許迴圈引用
			customizeBeanFactory(beanFactory);
			
			// 讀取配置檔案資訊,載入 Bean 到 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);
		}
	}
複製程式碼

可能大家之前會之前會認為ApplicationContext 與 BeanFactory 之間是繼承關係,但是在實際應用中,但是它不應該被理解為 BeanFactory 的實現類,ApplicationContext 是持有 BeanFactory 的一個例項,並且以後所有的 BeanFactory相關的操作其實是給這個例項來處理的。

簡單介紹一下 customizeBeanFactory 方法:

  • allowBeanDefinitionOverriding:設定Bean 是否可以被覆蓋
  • allowCircularReferences:設定是否可以迴圈依賴
	protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
		if (this.allowBeanDefinitionOverriding != null) {
			beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.allowCircularReferences != null) {
			beanFactory.setAllowCircularReferences(this.allowCircularReferences);
		}
	}
複製程式碼

5、AbstractXmlApplicationContext

核心方法:

org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)
複製程式碼

Spring原始碼分析:Spring IOC容器初始化

經歷了那麼長的路程,才終於到了XML -> Resource -> BeanDefinition 這個步驟:

	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		//給這個 BeanFactory 例項化一個 XmlBeanDefinitionReader BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// 設定預設環境配置
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// 初始化 BeanDefinitionReader
		initBeanDefinitionReader(beanDefinitionReader);
		// 將配置檔案讀取,解析成BeanDefinition
		loadBeanDefinitions(beanDefinitionReader);
	}
複製程式碼

這個方法比較簡單,我們直接往下走,進入到loadBeanDefinitions()方法中:

	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			reader.loadBeanDefinitions(configLocations);
		}
	}
複製程式碼

可以看到,上面主要分成了兩個步驟,一個是通過Resource 進行解析,一個是通過configLocations 進行解析(這個步驟,主要多了將資源定位符抓換為Resource 的過程)。

後續的Bean 解析,將會在下述方法進行解析:

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)
複製程式碼

6、總結

由於篇幅原因,本文主要介紹了Spring IOC 容器建立的原始碼過程,關於Bean 解析,以及後續步驟的原始碼分析,將會在後續補上。最後我們來回顧一下主流程:

  • ClassPathXmlApplicationContext.ClassPathXmlApplicationContext() 初始化
  • AbstractApplicationContext.refresh() 核心方法
  • AbstractRefreshableApplicationContext.refreshBeanFactory() 初始化BeanFactory
  • AbstractXmlApplicationContext.loadBeanDefinitions() 解析 Resource
  • 未完待續。。。

參考資料:

  • https://docs.spring.io/spring/docs/5.0.5.BUILD-SNAPSHOT/spring-framework-reference/core.html#spring-core
  • http://www.importnew.com/27469.html
  • http://www.importnew.com/19243.html

相關文章