Spring 工程執(zhí)行過程
1. 前言
Spring 框架是如何工作的?
本節(jié)目的在于幫助大家理解 Spring 框架底層干了什么事情。
在上一節(jié)中我們通過一個(gè)入門工程簡(jiǎn)單地體驗(yàn)了一把 Spring 的使用。
我們發(fā)現(xiàn),通過構(gòu)造一個(gè) ClassPathXmlApplicationContext
對(duì)象,加載項(xiàng)目的 applicationContext.xml
文件,確實(shí)可以實(shí)例化對(duì)象。
疑問導(dǎo)出
而腦海中不禁有一個(gè)想法… Spring 如何初始化對(duì)象的實(shí)例的?我們又如何從容器中獲取得到對(duì)象的實(shí)例的呢?
帶著疑問… 開啟本節(jié)的源碼和原理之旅。
2. 容器初始化
回顧代碼:
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService service = (UserService) context.getBean("userService");
service.saveUser();
}
在上面的代碼中可以得知 Spring 的容器是 ApplicationContext
,那么它到底是什么東西呢?先跟我一起追蹤一下它的角色。
官方文檔
慕課解釋
簡(jiǎn)單翻譯過來就是 ApplicationContext
是一個(gè) 接口,是 BeanFactory
這個(gè)接口的子接口,它擴(kuò)展了 BeanFactory
這個(gè)接口,提供了額外附加的功能。
而 BeanFactory
是管理 bean 對(duì)象的容器的根接口,大家了解下就好,我們是針對(duì)它的子接口 ClassPathXmlApplicationContext
做的實(shí)例化,目的是加載項(xiàng)目中的 Spring 的配置文件,使 Spring 來管理我們定義的 bean 對(duì)象。
疑問導(dǎo)出
那么我們的問題是…ClassPathXmlApplicationContext
對(duì)象實(shí)例化之后,干了哪些事情呢?
2.1 容器初始化執(zhí)行動(dòng)作
applicationContext
實(shí)例化執(zhí)行代碼邏輯 。
我們追蹤下源碼,發(fā)現(xiàn) ClassPathXmlApplicationContext
初始化的時(shí)候,它做了一系列的事情。源碼如下:
代碼解釋:
- 是初始化
ClassPathXmlApplicationContext
對(duì)象執(zhí)行的有參構(gòu)造; - 加載項(xiàng)目下的 xml 配置文件;
- 調(diào)用 refresh 刷新容器的方法 bean 的實(shí)例化就在這個(gè)方法中。
繼續(xù)跟蹤:
2.2 容器初始化 bean 對(duì)象動(dòng)作
下面是從源碼中粘貼的部分代碼
步驟闡述:
對(duì)于我們而言 這些英文看起來很吃力… 放輕松大家,我們只關(guān)注對(duì)我們理解流程有用的代碼:
- 1 的位置:是準(zhǔn)備刷新,那么 Spring 只是設(shè)置刷新的標(biāo)記,加載了外部的
properties
屬性文件; - 2 的位置:是準(zhǔn)備 bean 工廠對(duì)象;
- 3 的位置:這一步驟就加載了配置文件中的所有 bean 標(biāo)簽,但是并沒有對(duì)他們進(jìn)行實(shí)例化;
- 4 的位置:完成此上下文的 bean 工廠的初始化,初始化所有剩余的單例 bean。(Spring 中默認(rèn)加載的 bean 就是單例模式后面生命周期會(huì)講)
- 最后的位置:完成容器的刷新,也就是所有的 bean 初始化完成。
//這里粘貼一部分初始化代碼的邏輯 幫助大家理解
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
// Trigger initialization of all non-lazy singleton beans...
//所有非懶加載的單例bean的觸發(fā)器初始化。。。
for (String beanName : beanNames) {
...//省略循環(huán)的代碼
}
OK 上面就是加載配置文件后 Spring 框架做的所有事情,當(dāng)然實(shí)際底層涉及的東西 更多,但是我們沒有必要深究,畢竟我們是理解過程,不是追求實(shí)現(xiàn)。
疑問導(dǎo)出:
我們整理了 Spring 初始化 bean 對(duì)象的過程,那么如果容器中確實(shí)存在了 bean 的實(shí)例,我們是如何獲取得到的呢?
3. 容器中獲取對(duì)象的過程
還是先看下我們獲取容器對(duì)象的代碼:
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService service = (UserService) context.getBean("userService");
service.saveUser();
}
代碼分析:
context.getBean
的方法是通過 bean 標(biāo)簽里的 id 來從容器中獲取,那么我們看下源碼 :
在父類 AbstractApplicationContext
中有對(duì) getBean 方法的實(shí)現(xiàn)。
@Override
public Object getBean(String name) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(name);
}
追蹤父類方法
最終通過我們層層追蹤,我們?cè)?AbstractAutowireCapableBeanFactory
中發(fā)現(xiàn)這樣的一段代碼:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
//...
//省略大量方法內(nèi)部代碼
//...
// Initialize the bean instance.
Object exposedObject = bean;
try {
//給實(shí)例中的屬性賦值
populateBean(beanName, mbd, instanceWrapper);
//真實(shí)實(shí)例化對(duì)象
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
//...
//繼續(xù)省略大量方法
//...
// Register bean as disposable.
try {
//將實(shí)例化后的對(duì)象放入容器中
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
//返回實(shí)例化后的對(duì)象實(shí)例
return exposedObject;
}
上面源碼中我們可以看到: 對(duì)象實(shí)例的獲取好像是在獲取的時(shí)候執(zhí)行的 doCreateBean
,那么之前記載的 xml
文件不是實(shí)例過了嗎?稍微解釋下:加載文件時(shí)候的實(shí)例化操作,其實(shí)是實(shí)例化了一個(gè) Spring 框架提供的對(duì)象,作用是對(duì)于我們 bean 對(duì)象做描述,這里才是真實(shí)的實(shí)例化動(dòng)作。我們?cè)倏纯?registerDisposableBeanIfNecessary
這個(gè)方法做的是什么。
public void registerDisposableBean(String beanName, DisposableBean bean) {
synchronized (this.disposableBeans) {
this.disposableBeans.put(beanName, bean);
}
}
結(jié)論
一切真相大白。它其實(shí)就是一個(gè) map 集合 ,這個(gè) map 集合的 key 就是我們定義的 bean 的 id 或者 bean 的 name ,那么值就是對(duì)象的實(shí)例。
4. 小結(jié)
本章節(jié) 帶著大家梳理了一下 Spring 初始化 bean 和獲取 bean 的流程:
- Spring 框架通過 ResourceLoader 加載項(xiàng)目的 xml 配置文件;
- 讀取 xml 的配置信息 變成對(duì)象存儲(chǔ),但未實(shí)例化;
- 通過 bean 工廠處理器對(duì) bean 做實(shí)例化,存儲(chǔ)到一個(gè) map 集合中默認(rèn)是單例;
- 獲取對(duì)象 通過 xml 文件中 bean 的 id 從 map 集合中通過 get (key) 獲取。
羅馬不是一天建成的 ,書山有路勤為徑…