探索SpringBoot-一起來看看Spring容器載入核心原始碼(六)
前文回顧
上篇寫了探索SpringBoot-結合idea搭建Maven工程 續(五),好了,基本上關於Idea
這塊暫時先告一個段落了。下面正式來探索下Spring Boot
相關的內容。
探索Spring 物件工廠能力
Spring2.x時代
在learn-spring-framework-2.x
中的pom檔案中,加上最新的spring-context
依賴。表示在這個模組中,我們使用的依賴是spring2.x
。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>2.0.8</version>
</dependency>
複製程式碼
因為spring2.x
時,必須需要編寫xml
檔案來載入spring上下文
。所以,我們除了啟動類之後,還打算編寫三個xml
檔案。
- configurable-context.xml:表示需要載入的上下文
- dev-context.xml:表示dev環境下需要載入的Bean
- prod-context.xml:表示prod環境下需要載入的Bean
在configurable-context.xml
中,我們使用import
和Java的System Property
來引入不同環境所需要的上下文。
<import resource="classpath:/META-INF/${env}-context.xml" />
複製程式碼
在dev-context.xml
和prod-context.xml
中分別引入相同名稱的Bean
,但是這個Bean
的存在不同的屬性值。
dev-context.xml
<!-- dev 環境 value Bean 定義-->
<bean id="name" class="java.lang.String">
<constructor-arg>
<value>shane</value>
</constructor-arg>
</bean>
複製程式碼
prod-context.xml
<!-- prod 環境 name Bean 定義-->
<bean id="name" class="java.lang.String">
<constructor-arg>
<value>微秒</value>
</constructor-arg>
</bean>
複製程式碼
最後定義啟動類,啟動類顯示載入Spring
上下文,並輸出id
為name
的Bean
的屬性值。當然會根據System Property
的內容來動態載入不同環境下的Bean
,並且輸出不同的值。
這麼做,也是為了演示Spring
最最基礎的功能,作為一個物件工廠的能力。
ConfigurableApplicationContextBootstrap.java
public class ConfigurableApplicationContextBootstrap {
static {
// 調整系統屬性 "env",實現 "name" bean 的定義切換
// envValue 可能來自於 "-D" 命令列啟動引數
// 引數當不存在時,使用 "prod" 作為預設值
String envValue = System.getProperty("env","dev");
System.setProperty("env",envValue);
}
public static void main(String[] args) {
// 定義 XML ApplicationContext
// 先留意下這個location的方式,不需要寫classpath的字首
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/configurable-context.xml");
// "name" bean 物件
String value = (String) context.getBean("name");
// "name" bean 內容輸出
System.out.println("Bean 'name' 的內容為:" + value);
// 關閉上下文
context.close();
}
複製程式碼
控制檯輸出
思考下Spring
在這個過程中,做了什麼事情呢?
- 載入
xml
檔案,初始化Spring上下文
- 獲取
Spring上下文
中的物件
ClassPathXmlAppliationContext原始碼初步分析
我們進入到ClassPathXmlApplicationContext(String)
建構函式中,可以發現呼叫了另外一個建構函式ClassPathXmlApplicationContext(String[] configLocations,boolean refresh,ApplicationContext parent)
而且設定了refresh
引數為true
,parent
引數為null
。
/**
* Create a new ClassPathXmlApplicationContext,loading the definitions
* from the given XML file and automatically refreshing the context.
* @param configLocation resource location
* @throws BeansException if context creation failed
*/
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation},true,null);
}
/**
* Create a new ClassPathXmlApplicationContext with the given parent,* loading the definitions from the given XML files.
* @param configLocations array of resource locations
* @param refresh whether to automatically refresh the context,* loading all bean definitions and creating all singletons.
* Alternatively,call refresh manually after further configuring the context.
* @param parent the parent context
* @throws BeansException if context creation failed
* @see #refresh()
*/
public ClassPathXmlApplicationContext(String[] configLocations,boolean refresh,ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
複製程式碼
通過註釋可以知道建構函式的作用是用給定的parent建立一個ClassPathXmlApplicationContext,並且根據XML檔案載入定義的物件
- configLocations: 資原始檔的地址
- refresh:是否自動重新整理上下文,載入所有的
bean
定義並且建立所有的單例物件 - parent:父類上下文(暫時不理解也沒有關係,
Spring
是存在父子上下文的,之後有機會講到)
refresh函式
建構函式首先解析了資原始檔並設定為上下文的一個屬性,之後進入到了關鍵的refresh
函式中。
public void refresh() throws BeansException,IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing. [1.為refresh準備上下文]
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.[2.告知子類refresh內部bean工廠]
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.[3.準備需要在本次上文中使用的bean工廠]
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.[4.允許上下文子類的bean工廠呼叫初始化函式]
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.[5.呼叫在上下文的註冊的工廠處理器]
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.[6.註冊在bean建立的過程中的攔截處理器]
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.[7.初始化訊息源]
initMessageSource();
// Initialize event multicaster for this context.[8.初始化事件多播機制]
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.[9.在這個特定的上下文子類中初始化其他特殊的beans]
onRefresh();
// Check for listener beans and register them.[10.檢查監聽器的beans並且註冊他們]
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.[11.初始化存在的單例,不包括懶載入的物件]
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.[12.最後一步:釋出相關的事件]
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
beanFactory.destroySingletons();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
複製程式碼
看我的中文註釋
,總共有12步核心步驟來初始化ClassPathXmlApplicationContext
。下面我們在一步步來分析。
prepareRefresh函式
首先看prepareRefresh()
函式。
/**
* Prepare this context for refreshing,setting its startup date and
* active flag.
*/
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
synchronized (this.activeMonitor) {
this.active = true;
}
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
}
複製程式碼
可以看到就只有兩步核心操作。首先記錄了當前的時間,然後嘗試獲取activeMonitor
的鎖。可以activeMonitor
的作用後面聯絡起來再分析。
下一篇分析obtainFreshBeanFactory()
函式,一步步來,畢竟是探索系列
嘛,不知道的內容,不斷地探索
,我們才能將其轉換為我們知道的東西,這就是學習。
當然這是Spring
部分的核心的原始碼,不過因為SpringBoot
其實是構建在Spring
基礎之上的,所以Spring
的部分原始碼也會有講解。
關於寫作
以後這裡每天都會寫一篇文章,題材不限,內容不限,字數不限。儘量把自己每天的思考都放入其中。
如果這篇文章給你帶來了幫助,能請你寫下是哪個部分嗎?有效的反饋是對我最大的幫助。
我是shane。今天是2019年8月11日。百天寫作計劃的第十八天,18/100。