Spring IOC 啟動過程


1. 引言

本篇博文主要介紹 IOC 容器的啟動過程,啟動過程分為兩個步驟,第一個階段是容器的啟動階段,第二個階段是 Bean 實例化階段,這兩個階段各自需要執行的步驟如下圖,接下來會一一介紹。

需要注意的是,在 Spring 中,最基礎的容器接口方法是由 BeanFactory 定義的,而 BeanFactory 的實現類采用的是 延遲加載,也就是說,容器啟動時,只會進行第一個階段的操作, 當需要某個類的實例時,才會進行第二個階段的操作。而 ApplicationContext(另一個容器的實現類)在啟動容器時就完成了所有初始化,這就需要更多的系統資源,我們需要根據不同的場景選擇不同的容器實現類。

2. 容器啟動階段

2.1 BeanDefinitionRegistry 介紹

在介紹如何加載配置文件之前,先了解一些基礎知識。BeanFactory 只是一個接口,它需要一個實現類,DefaultListableBeanFactory 就是一個比較常用的實現類,它還實現了 BeanDefinitionRegisitry 接口,該接口在容器中擔任 Bean 注冊的角色。

打個比方,BeanDefinitionRegistry 就像圖書館上的書架,所有的書是放在書架上的。雖然還書借書都是跟圖書館(也就是 BeanFactory,或者說 BookFactory)打交道,但書架才是圖書館存放各類圖書的地方。所以,書架對於圖書館來說,就是他的 BookDefinitionRegistry。

而每一本書都應該有自己唯一的標識,在容器中每個實例也應該有這樣的標識,這些標識是由 BeanDefinition 存儲的。它負責保存對象的所有必要信息,例如對象的 class 類型、是否是抽象類、構造方法參數以及其他屬性等等,當客戶端向 BeanFactory 請求相應對象時,BeanFactory 會通過這些信息返回一個完備可用的對象實例。

2.2 加載配置文件

接下來介紹如何加載配置文件。IOC 的理念是 Don't call us, we will call you。當一個類中需要另外一個類的實例時,我們並不用手動去創建,IOC 容器會幫我們完成這個任務,但我們需要告訴它哪些類之間存在依賴,例如 A 類中依賴的是哪個類,B 類依賴的類是在哪個包下面,而這些信息我們可以使用注解或者配置文件的方式告訴 IOC 容器。

這里主要講下讀取配置 IOC 是如何讀取配置文件的。IOC 容器讀取配置文件的接口為 BeanDefinitionReader,它會根據配置文件格式的不同給出不同的實現類,將配置文件中的內容讀取並映射到 BeanDefinition 中,整個過程可以通過如下代碼表示:

public static void main(String[] args){
    DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
    BeanFactory container = (BeanFactory)bindViaPropertiesFile(beanRegistry);
    FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
    newsProvider.getAndPersistNews();
}
public static BeanFactory bindViaPropertiesFile(BeanDefinitionRegistry registry){
    BeanDefinitionRegistry beanRegistry = <某個 BeanDefinitionRegistry 實現類,通常為 DefaultListableBeanFactory>;
    BeanDefinitionReader beanDefinitionReader = new BeanDefinitionReaderImpl(beanRegistry);
    // 讀取配置文件的核心方法
    beanDefinitionReader.loadBeanDefinitions("配置文件路徑");
    return (BeanFactory)registry;
}

2.3 解析配置文件

在上面讀取配置文件的步驟中,僅僅是將 <bean> 中的屬性值讀取出來,這只是一些字符串,最終應用程序卻是由各種類型的對象實例構成的,我們需要將這些字符串轉換成類信息,這個轉化過程就需要定義一個規則,這些規則是由 PropertyEditor 定義,由 CustomEditorConfigurer 幫我們傳遞給 Spring 容器。經過 PropertyEditor 定義的規則將字符串轉換為對應的類型信息之后並存儲在 BeanDefinition 中,交給 BeanDefinitionRegistry 管理,容器的啟動過程就完成了。其初始化階段可以用一張圖來表示:

3. Bean 實例化階段

Bean 實例化過程如下圖:

3.1 Bean 的實例化

容器在內部實現 Bean 實例化時,采用 策略模式 來決定使用何種方式初始化 bean 實例。InstantiationStrategy 定義了實例化策略的接口,SimpleInstantiationStrategy 繼承了它,主要通過 反射 來實現對象的實例化。CglibSubclassingInstantiation 繼承了 SimpleInstantiationStrategy 以反射方式實例化的功能,並且還有 CGLIB 動態字節碼 生成實例的功能。容器默認采用后者實現。

但是需要注意的是,InstantiationStrategy 實例化對象后並沒有直接將對象返回,而是用 BeanWrapper 進行包裝,方便后續對此實例進行 屬性的設置。其設置的依據是通過上面所講的 PropertyEditor 接口,在第一步構造完成對象之后,Spring 會根據對象實例構造一個 BeanWrapperImpl 實例,然后將之前 CustomEditor-
Configurer 注冊的 PropertyEditor 復制一份給 BeanWrapperImpl 實例這樣,當 BeanWrapper 轉換類型、設置對象屬性值時,就不會無從下手了。

3.2 BeanPostProcessor

當對象實例化完成之后,會將對象實例傳到 BeanPostProcessor,這個接口的定義如下:

public interface BeanPostProcessor{
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

這個接口為我們擴展對象實例的行為提供了極大的便利,其中 postProcessBeforeInitialization 對應的就是上圖中 BeanPostProcessor 的前置處理,postProcessAfterInitialization 對應的就是 上圖中 BeanPostProcessor 的后置處理。Spring 的 AOP 就是使用 BeanPostProcessor 來為對象生成相應的代理對象。

3.3 Bean 的初始化

在對象實例話過程調用 BeanPostProcessor 的前置處理 之后,會接着檢測對象是否實現了 InitializingBean 接口,如果是,則會調用該接口的 afterPropertiesSet 方法進一步調整對象實例的狀態。但是,如果僅僅為了做一個初始化動作而去實現一個接口這樣未免有點小題大做,因此 Spring 有提供了另一種方法,就是在 <bean> 中配置 init-method 屬性,指定一個方法做對象初始化前的操作。到了這個步驟,對象實例化也快接近尾聲了。

3.4 Bean 的銷毀

當所有的一切,該設置的設置,該注入的注入,該調用的調用之后,容器將會檢查 singleton 類型的 bean 實例,看起是否實現了 DisposableBean 接口,或者查看對應的 bean 定義是否通過 <bean> 的 destroy-method 屬性指定了自定義的對象銷毀方法。如果是,就會為該實例注冊一個用於對象銷毀的回調(Callback),以便在這些 singleton 類型的對象實例銷毀之前,執行銷毀邏輯。至此,Bean 對象的實例化階段也完成了。

4. 總結

本篇博文主要是對 Spring IOC 容器初始化過程中涉及到的重點接口進行了講解,對於整個啟動過程並沒有做一個清晰的梳理,有需要還是建議解析其他的博客以前學習,我覺得這樣效果更佳。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM