Spring原始碼系列 —— 構造和初始化上下文

懷瑾握瑜XI發表於2018-12-22
探索spring原始碼實現,精華的設計模式,各種jdk提供的陌生api,還有那麼點黑科技都是一直以來想做的一件事!但是讀原始碼是一件非常痛苦的事情,需要有很大的耐心和紮實的基礎。
 

在曾經讀兩次失敗的基礎上,這次希望能一站到底!這個系列基於spring v4.3.20版本探索。 

Spring上下文啟動載入過程的分段

spring上下文的實現非常多,其中基於Xml啟動的有ClassPathXmlApplicationContext、FileSystemXmlApplicationContext等等。這些上下文都非常類似,基於解析Xml,獲取配置的bean,最終完成上下文的啟動載入過程。 這裡以ClassPathXmlApplicationContext為主分析Spring ApplicatonContext整個啟動過程。

這裡將spring上下文的啟動分為兩個大步驟: 

  • 上下文物件本身的構造和初始化:建立ClassPathXmlApplicationContext物件,構造器中執行一些初始化操作,如:設定上下文的環境Enviroment、設定上下文的資源解析器等
  • 上下文獲取bean配置,解析例項化bean:構造BeanFactory,解析Xml,構造bean定義,依賴注入,例項化bean。這個過程非常複雜;

 Spring原始碼系列 —— 構造和初始化上下文

這節主要分析第一個階段:上下文物件本身的構造和初始化。

上下文物件本身的構造和初始化

編寫debug程式碼,構造ClassPathXmlApplicationContext物件:

ApplicationContext context = new ClassPathXmlApplicationContext("/beans.xml");
HelloWorldBean h = context.getBean("helloWorld", HelloWorldBean.class);
h.printHelloWorld();
複製程式碼

編寫配置檔案beans.xml,配置helloWorld的bean:

<bean id="helloWorld" class="com.learn.ioc.beans.HelloWorldBean"></bean>
複製程式碼

下面主要分析ClassPathXmlApplicationContext new的過程。ClassPathXmlApplicationContext建構函式如下:

// 使用beans.xml作為上下文的配置檔案構造ApplicationContext物件
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
	this(new String[] {configLocation}, true, null);
}

// 以配置檔案、是否重新整理上下文、父上下文作為引數構造ApplicationContext物件
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
		throws BeansException {
	super(parent);
	setConfigLocations(configLocations);
	if (refresh) {
		refresh();
	}
}
複製程式碼

super(parent)和setConfigLocations(configLocations)方法對應兩階段中的第一階段:上下文物件本身的構造和初始化。refresh()方法完成第二階段:上下文獲取bean配置,解析例項化bean

 super(parent)以該上下文的父上下文作為引數呼叫父類的構造器,這裡由於沒有父上下文,所以為null

public AbstractXmlApplicationContext(ApplicationContext parent) {
	super(parent);
}
複製程式碼

AbstractXmlApplicationContext是以Xml作為配置的Spring上下文,AbstractXmlApplicationContext又繼續呼叫其父類的構造器:

public AbstractRefreshableConfigApplicationContext(ApplicationContext parent) {
	super(parent);
}
複製程式碼

AbstractRefreshableConfigApplicationContext也是以Xml檔案作為基礎配置的上下文,但是它具有可以重新整理配置檔案的能力,AbstractRefreshableConfigApplicationContext中提供了setConfigLocations方法可以用於設定配置檔案。是Xml配置檔案上下文的基石。 該上下文中又呼叫父類建構函式: 

public AbstractRefreshableApplicationContext(ApplicationContext parent) {
	super(parent);
}
複製程式碼

Spring上下文可以稱作為上下文家族,繼承有多代。其中AbstractRefreshableApplicationContext是上下文中家族中的元老。它提供了兩個非常重要的介面refreshBeanFactory()和loadBeanDefinitions(DefaultListableBeanFactory beanFactory)用於配置BeanFactory和定義接下載入Bean的介面。雖然建構函式中依然呼叫父類的建構函式,但是它的確非常重要:

public AbstractApplicationContext(ApplicationContext parent) {
	this();
	// 設定該上下文的父上下文
	setParent(parent);
}

public AbstractApplicationContext() {
	// 初始化資源解析器,用於解析獲取Xml配置
	this.resourcePatternResolver = getResourcePatternResolver();
}
複製程式碼

上下文家族的鼻祖AbstractApplicationContext中定義了整個Bean的宣告週期和處理過程。在構造器中初始化resourcePatternResolver資源解析器,賦予ApplicationContext具有解析資源的能力。且設定父上下文。 

 getResourcePatternResolver主要用來建立資源解析器:

protected ResourcePatternResolver getResourcePatternResolver() {
	return new PathMatchingResourcePatternResolver(this);
}
複製程式碼

PathMatchingResourcePatternResolver主要以路徑匹配的模式進行解析獲取資源,其主要能力和實現後續會詳細介紹。 

 setParent(parent)主要用於設定該上下文的父上下文:

public void setParent(ApplicationContext parent) {
	// 設定父上下文
	this.parent = parent;
	// 如果父上下文不空
	if (parent != null) {
	  	// 獲取父上下文的環境變數Environment
		Environment parentEnvironment = parent.getEnvironment();
		if (parentEnvironment instanceof ConfigurableEnvironment) {
			// 將父上下文的環境變數Environment合併至該級上下文環境變數中
			getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
		}
	}
}
複製程式碼

經過一系列的父類的構造器的呼叫,Spring上下文完成了多級上下文的載入過程。可以從下圖看出其繼承順序關係:

Spring原始碼系列 —— 構造和初始化上下文

Tips 
上下文的實現中設計模式非常強,秉著設計的六大原則進行實現: 
單一職責原則:每種上下文都有自己的職責能力,使得上下文的擴充套件能力極強; 
開閉原則:AbstractRefreshableApplicationContext提供了對loadBeanDefinitions的定義由其子類按照不同載入Bean的邏輯各自實現; 
依賴倒置原則:通過多級的抽象,提供了不同的介面,達到針對介面程式設計;  


ClassPathXmlApplicationContext中呼叫層級父類上下文物件的構造後,再執行AbstractRefreshableConfigApplicationContext中實現的setConfigLocations設定上下文的Xml配置:

// 設定配置檔案路徑
public void setConfigLocations(String... locations) {
	// 如果不為空,則構早配置檔案路徑,支援多個配置檔案
	if (locations != null) {
		Assert.noNullElements(locations, "Config locations must not be null");
		this.configLocations = new String[locations.length];
		// 迴圈解析多個配置檔案路徑
		for (int i = 0; i < locations.length; i++) {
			this.configLocations[i] = resolvePath(locations[i]).trim();
		}
	}
	else {
		this.configLocations = null;
	}
}
複製程式碼

關於resolvePath的具體過程涉及到Spring IOC容器的環境Enviroment元件,具體的解析路徑的過程將在下章的探索Enviroment中詳解。 

總結

Spring上下文型別非常繁多,其中有直接面向各種場景直接使用的FileSystemApplicationContext、ClasspathXmlApplicationContext等等,還有很多實現基礎能力的上下文: AbstractApplicationContext是上下文實現中的基石,其中定義了上下文Bean物件依賴注入的模板; AbstractRefreshableApplicationContext也是非常重要的上下文,其中組合了BeanFactory和定義載入Bean的介面;

相關文章