1. 程式人生 > 實用技巧 >1. spring5原始碼 -- Spring整體脈絡 IOC載入過程 Bean的生命週期

1. spring5原始碼 -- Spring整體脈絡 IOC載入過程 Bean的生命週期

可以學習到什麼?

0. spring整體脈絡

1. 描述BeanFactory

2. BeanFactory和ApplicationContext的區別

3. 簡述SpringIoC的載入過程

4. 簡述Bean的生命週期

5. Spring中有哪些擴充套件介面及呼叫機制

一. spring原始碼整體脈絡介紹及原始碼編譯

1.1. 什麼是IOC

ioc是控制反轉, 這是一種設計理念, 用來解決的是層和層之間, 類和類之間的耦合問題.

比如,現在有A, B兩個類, 在A類中引用了B類. 那麼如果有一天, B類要被替換掉, 我們會怎麼辦呢?如果B類被引用了100次, 我們要替換100次?

現在呢, A是直接呼叫B, 如果我們間接的呼叫B, 將B包裝起來, 如果以後將B換成C, 只需要在包裝類裡面替換就可以了. 我們不需要修改A類. 這就是控制反轉.

Spring使用了ioc, Spring.ioc(A, B) 將A和B的引用都存在ioc中, spring會幫我們維護好, 完全不用擔心.

當我們在A中要使用B的時候, 使用B對應的介面, 然後使用@Autowired註解

A {
   @Autowired
   private IB b;  
}

什麼時候把B換掉了, 不痛不癢的, 只需要把新的類放到IoC中就可以了.

1.2. Spring原始碼的整體脈絡梳理

Spring IoC是一個容器, 在Spring Ioc中維護了許多Bean

那這些bean是如何被註冊到IoC中的呢? 換句話說, 我們自定義的類, 是如何作為一個bean交給IoC容器去管理的呢?

先來回憶,我們在開發spring的時候的步驟:

第一步: 配置類. 配置類可以使用的方式通常由
      1) xml配置
      2) 註解配置
      3) javaconfig方式配置
第二步: 載入spring上下文
      1) 如果是xml, 則new ClassPathXmlApplicationContext("xml");
      2) 如果是註解配置: 則new AnnotationConfigApplicationContext(config.class)

第三步: getBean() 
我們會講自定義的類, 通過xml或者註解的方式注入到ioc容器中.

在這一步, 會將xml或註解中指定的類注入到IoC容器中.

1.2.1 那麼, 到底是如何將一個類注入到ioc中的呢?

下面就來梳理一下整個過程.

第一問: 一個類要生產成一個Bean, 最重要最核心的類是什麼?

是BeanFactory

第二問: BeanFactory是什麼呢?

BeanFactory是Spring頂層的核心介面--使用了簡單工廠模式. 通常都是根據一個名字生產一個例項, 根據傳入的唯一的標誌來獲得bean物件, 但具體是穿入引數後建立, 還是穿入引數前建立, 這個要根據 具體情況而定, 根據名字或型別生產不同的bean.

一句話總結: BeanFactory的責任就是生產Bean

來看下面這段程式碼:

public static void main( String[] args ) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.getBean("***");
    }

這段程式碼實現的功能是, 讀取當前檔案所在目錄及其子目錄中的檔案, 然後獲取指定名稱的bean, 整個流程如下圖所示:

首先, 通過ClassPathXmlApplicationContext或者AnnotationConfigApplicationContext去讀取配置,

然後將其交給BeanFactory.

第三. BeanFactory呼叫getBean()方法, 將Bean注入到IoC容器中

我們發現, 配置的讀取, 可能是xml方式, 也可能是annotation的方式,不同的方式讀取應該使用的是不同的工具. 那麼這些工具讀取的結果應該是統一的, 然後才能交給BeanFactory去處理.

因為在BeanFactory中是不會對這些異同點進行處理的.BeanFactory的作用只有一個, 就是個生產Bean.

1.2.2 那麼, 不同的工具讀取配置是如何統一的呢?

我們知道,讀取配置這一塊, 應該會有一個不同的實現. 將xml和註解方式讀取成統一的東西, 放入到beanFactory中. 這個東西是誰呢?就是BeanDefinition(Bean定義)

什麼意思呢? 如下圖:

看綠色框框住的部分. 這個含義是: 通過不同的工具, 可能是xmlApplicationContext, 可能是annotationApplicationContext工具 讀取的配置, 最後都會構造成BeanDefinition物件. 然後將BeanDefinition傳遞給BeanFactory, BeanFactory統一處理BeanDefinition物件, 呼叫getBean()方法, 將其放入IoC容器中.

1.2.3 那麼又是是如何讀取配置統一構造成BeanDefinition的呢?

我們來舉個例子, 現在有一個人, 比如說我剛買了一個房子, 我要裝修. 需要一個衣櫃, 這時候, 我會找到一個衣櫃店. 然後告訴他我的需求, 櫃子的顏色, 款式格式什麼樣. 然後衣櫃店記錄我的需求, 這個時候, 他不會自己生產, 他會通知工廠, 讓工廠來生產. 工廠按照什麼生產呢, 衣櫃店有一個設計師,他們的設計師. 會按照我的需求設計出一張圖紙. 然後將圖紙交給工廠. 工廠按照圖紙要求生產Bean.

整個過程如下圖:

入口是"我"

1. 我有一個需求, 打一個櫃子, 找到衣櫃店

2. 我告訴衣櫃店我的需求, 櫃子的顏色, 款式, 然後衣櫃店的設計師按照我的要求 ,設計出一張圖紙

3. 衣櫃店將圖紙給到工廠, 工廠按照圖紙生產櫃子

這是製造衣櫃的過程. 其中在畫圖紙的時候, 畫一張就給工廠給一張, 這樣效率太低了. 我們可以畫了n張, 一起給工廠. 所以, 在設計圖紙這塊是一個容器, 存放多張圖紙

後面,如果我還想定製一個櫥櫃店. 那麼, 就告訴設計師我的櫥櫃的顏色,款式, 就可以了. 流程和上面都是一樣的.

整個這個過程, 就類似於我們的bean生產的過程

1. 定義了一個帶有@Component註解的類, 我找到衣櫃店, 衣櫃店就類似於ApplicationContext.

2. 我告訴ApplicationContext我的需求, 我要懶載入@Lazy, 設定單例模式還是多例模式@Scope. 對應的就是定製櫃子的顏色,款式. 然後衣櫃店裡的設計師BeanDefinitionRegistry根據我的需求設計出圖紙, 也就是構造成BeanDefinition. 不同的BeanDefinitionRegistry設計出不同的BeanDefinition, 然後將他們都放在容器中.

3. 衣櫃店ApplicationContext統一將一摞圖紙BeanDefinitionMap交給工廠, 然後工廠按照要求生產Bean, 然後將生成的bean放入到IoC容器中.

這是一個帶有@Component的類被載入的過程.

衣櫃店要要想生意好, 那麼他要去拉活呀, 所以還需要好的銷售. 銷售要去掃樓盤, 去聯絡, 哪些人有裝修的需求. 挨個詢問.

可是問了100個人, 可能只有10個人有裝修的需求. 於是還要有一個接待, 這個接待要聯絡客戶, 看看哪些是有意向的客戶, 將其篩選出來. 然後定製傢俱.

這裡多了兩類人: 銷售和接待. 具體工作如下.

銷售就相當於我們的BeanDefinitionReader, 他的作用是去掃樓盤, 找到潛在客戶. 對應的就是BeanDefinitionReader去讀取xml配置或者Annotation註解.

xml中的配置有很多, 註解也有很多, 並不都是我們的目標. 於是有了接待

接待要去掃描所有潛在客戶. 將有意向的客戶掃描出來. 這就類似於我們的BeanDefinitionScanner, 去掃描潛在客戶, 最後將帶有@Component註解的類篩選出來

這就是後面需要定製傢俱的客戶了

BeanDefinitionReader對應的就去讀取配置類, 看看有哪些需求需要搞裝修.


它本身也是一個抽象類, 可以看到他有AnnotationBeanDefinitionReader和XmlBeanDefinitionReader


我們配置了配置包, 去掃描這個包下所有的類, 然後將掃描到的所有的類交給BeanDefinitionScanner, 它會去過濾帶有@Component的類. 

在和上面的流程連線起來, 就是整個配置檔案被載入到IoC的過程了.

1.3.ApplicationContext和FactoryBean的區別

1. FactoryBean的功能就是生產bean. 他生產bean是根據BeanDefinition來生產的. 所以, 一次只能生產一個

2. ApplicationContext有兩種. 一種是xmlApplicationContext, 另一種是annotationApplicationContext, 他傳入的引數是一個配置檔案. 也就是可以載入某個目錄下所有帶有@Component的類

他們兩個都各有使用場景. 使用ApplicationContext的居多.

另一個區別: 就是後面會說到的, ApplicationContext有兩個擴充套件介面, 可以用來和外部整合. 比如和MyBatis整合.

1.4. Bean的生命週期

如上圖, beanFactory拿到BeanDefinition, 直接呼叫getBean()就生產Bean了麼?

不是的, 生產Bean是有一個流程的. 下面我們來看看Bean的生命週期

第一步: 例項化. bean例項化的時候從BeanDefinition中得到Bean的名字, 然後通過反射機制, 將Bean例項化. 例項化以後, 這是還只是個殼子, 裡面什麼都沒有.

第二步: 填充屬性. 經過初始化以後, bean的殼子就有了, bean裡面有哪些屬性呢? 在這一步填充


第三步: 初始化. 初始化的時候, 會呼叫initMethod()初始化方法, destory()初始化結束方法

這個時候, 類就被構造好了.

第四步: 構造好了的類, 會被放到IoC的一個Map中. Map的key是beanName, value是bean例項. 這個Map是一個單例池, 也就是我們說的一級快取


第五步: 我們就可以通過getBean("user"), 從單例池中獲取雷鳴是user的類了.

在構造bean的過程中, 還會有很多細節的問題, 比如迴圈依賴.

A類裡面呼叫了B類, 所以BeanFactory在構造A的時候, 會去構造B. 然後在構造B的時候, 發現, B還依賴了A. 這樣, 就是迴圈依賴. 這是不可以的.

Spring是如何解決迴圈依賴的問題的呢?

設定出口. 比如A在構造的過程中, 那麼設定一個標記, 正在構造中. 然後構造B, B在構造的過程中應用了A, 這時候, 有趣構造A, 然後發現A正在構造中, 那麼, 就不會再次構造A了.

後面還會詳細講解Spring是如何解決迴圈引用的. 這裡我們需要知道的是:Spring使用的是三級快取來解決迴圈引用的問題

其實, bean是存在一級快取裡面, 迴圈引用使用的是三級快取來解決的. 其實, 一、二、三級快取就是Map。

1.5. Spring中的擴充套件介面

有兩個非常重要的擴充套件介面.BeanFactoryPostProcessor(Bean工廠的後置處理器) 和BeanDefinitionRegistryPostProcessor

這兩個介面是幹什麼的呢?

我們在這個圖裡面, 看到了設計師要設計出圖紙, 然後把圖紙交給工廠去生產. 那麼設計師設計出來的圖紙, 有沒有可能被修改呢?

當然是可以被修改的. 只要還沒有交給工廠, 就可以修改.

BeanFactoryPostProcessor(Bean工廠的後置處理器)的作用就是修改BeanDefinition.

1. BeanFactoryPostProcessor: 修改BeanDefinition.

是一個介面, 我們的類可以實現這個介面, 然後重寫裡面的方法

public class DefinedPost implements BeanFactoryPostProcessor {

    /**
     * 重寫Bean工廠的後置處理器
     * @param beanFactory
     * @throws BeansException
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // beanFactory 拿到工廠了, 就可以獲取某一個Bean定義了
        GenericBeanDefinition car = (GenericBeanDefinition) beanFactory.getBeanDefinition("Car");
        // 拿到了car, 然後修改了Car的類名為com.example.tulingcourse.Tank. 那麼後面在獲取的Bean裡面, 將其轉換為Car, 就會報錯了
        car.setBeanClassName("com.example.tulingcourse.Tank");
    }
}

第一步: 實現了BeanFactoryPostProcessor介面, 然後需要重寫裡面的方法

第二步: 我們發現重寫方法直接給我們了beanFactory, bean工廠

第三步: 拿到bean工廠, 我們就可以根據名稱獲取BeanDefinition, 也就是bean定義了.

第四步: 我們修改了bean定義中的類名為Tank.

這時候會發生什麼呢? 從bean工廠中構建的car, 取出來以後轉換成Car物件, 會報錯,

public static void main(String[] args) {
    

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
    
        Car car = context.getBean("car", Car.class); // 這裡會報錯, 因為已經被修改
        System.out.println(car.getName());
    }

執行流程: 當spring啟動的時候, 就會去執行AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TulingCourseApplication.class);

然後ApplicationContext回去掃描所有實現了BeanFactoryPostProcessor物件的類, 然後執行postProcessBeanFactory方法.

BeanFactoryPostProcessor被使用到的場景非常多, 在整合其他元件的時候, 比如整合mybatis

2.BeanDefinitionRegistryPostProcessor 註冊BeanDefinition

這是一個Bean定義註冊的後置處理器.BeanDefinitionRegistryPostProcessor本事是實現了BeanFactoryPostProcessor 介面

我們來看個demo

public class DefinedPost implements BeanDefinitionRegistryPostProcessor {

    /**
     * 重寫Bean工廠的後置處理器
     * @param beanFactory
     * @throws BeansException
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // beanFactory 拿到工廠了, 就可以獲取某一個Bean定義了
        GenericBeanDefinition car = (GenericBeanDefinition) beanFactory.getBeanDefinition("Car");
        // 拿到了car, 然後修改了Car的類名為com.example.tulingcourse.Tank. 那麼後面在獲取的Bean裡面, 將其轉換為Car, 就會報錯了
        car.setBeanClassName("com.example.tulingcourse.Tank");
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {

    }
}

一個類實現了BeanDefinitionRegistryPostProcessor, 需要重寫postProcessBeanDefinitionRegistry方法, 這個方法直接將BeanDefinitionRegistry就給我們了.

然後使用beanDefinitionRegistry.registerBeanDefinition(); 就可以新增圖紙了

在這裡可以註冊新的bean, 也可以刪除註冊的bean. 多註冊一個, bean工廠就要多構建一個.

總結:

BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor這兩個擴充套件類是很重要的類,這對於向外部擴充套件起到了很大的的作用, 比如: 整合mybatis

BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor是在ApplicationContext中的兩個擴充套件介面. 這也是ApplicationContext和BeanFactory的區別之一, 因為有了這兩個擴充套件節點, 就可以和外部做整合. 比如Mybatis整合. 比如: 掃描配置類, 就是通過這兩個擴充套件點的方式實現的.

這個擴充套件點的作用:

1. 除了IoC, 其他的擴充套件,比如AOP, 和MyBatis整合, 都要用到這兩個擴充套件點. 之所以Spring能夠有序不亂的和很多外部元件整合, 都是這兩個擴充套件點的功能

1.6 Bean的擴充套件點

除了ApplicationContext有擴充套件點, 在Spring IoC中的bean也有擴充套件點. BeanPostProcessor(Bean的後置處理器). 如果使用在getBean()之前, 那麼可以阻止構建Bean, 還可以自定義構建Bean.

BeanPostProcessor使用的場景有很多. 在Bean例項化之前和之後會被呼叫. 在填充屬性之前和之後也會被呼叫, 初始化之前和之後也會呼叫. 有些過程不只呼叫一次. 整個過程一共會呼叫9次. 在每一個過程都可以擴充套件Bean.

思考: Spring加入AOP是如何實現呢?

整合AOP肯定不會和IoC糅合在一塊了. AOP就是通過BeanPostProcessor(Bean後置處理器)整合進來的.

AOP的實現方式有兩種: 一種是CGLIB, 另一種是JDK.

假如說要進行整合, 會在那個步驟繼承呢? 比如要加日誌, 使用AOP的方式加. 我們通常是在初始化之後加AOP. 在這裡將AOP整合進來.

如上圖: 當面試的時候面試官問你, Bean的生命週期, 我們不能只說例項化-->填充屬性-->初始化. 還需要說初始化的時候, 還有一些列的aware.

1.7. Spring IOC的載入過程

對照上圖, 我們來簡述ioc的載入過程

我們將一個類載入成Bean, 不是一步到位的,需要經歷一下的過程.

1. 首先, 我們要將類載入成BeanDefinition(Bean定義)

  載入成bean定義, 有以下幾個步驟:

  1) 使用BeanDefinitionReader載入配置類, 此時是掃描所有的xml檔案或者專案中的註解. 這裡面有些使我們的目標類, 有些不是

  2) 使用BeanDefinitionScanner掃描出我們的目標類.

  3) 使用BeanDefinitionRegistry註冊bean到BeanDefinitionMap中.

2. 然後, ApplicationContext可以呼叫BeanFactoryPostProcessor修改bean定義, 還可以呼叫BeanDefinitionRegistryPostProcessor註冊bean定義

3. 將BeanDefinition交給BeanFactory處理, BeanFactory呼叫getBean()生成Bean或者呼叫Bean(getBean()有兩個功能).

4. 成產bean的時候, 首先會例項化, 然後填充屬性(主要是讀取@Autowire, @Value等註解). 在初始化Bean, 這裡會呼叫initMethod()方法和初始化銷燬方法destroy(). 初始化的時候還會呼叫一堆的Aware, 而且在bean生成的過程中 會有很多擴充套件點, 供我們去擴充套件.

5. 將生產出的Bean放入到Map中, map是一個一級快取池. 後面, 我們可以通過getBean("user")從快取池中獲取bean

1.9 Spring原始碼編譯過程演示

as