Spring原始碼系列 —— 構造和初始化上下文
探索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。這個過程非常複雜;
這節主要分析第一個階段:上下文物件本身的構造和初始化。
上下文物件本身的構造和初始化
編寫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上下文完成了多級上下文的載入過程。可以從下圖看出其繼承順序關係:
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的介面;