ApplicationContext refresh 過程及一些重要的 processor 解析

  • 2019 年 11 月 10 日
  • 筆記

回顧

上文 其實我們已經實現了一個簡單的 BeanFactory 它具的功能有

  • 註冊 Bean 到容器,通過限定名獲取 Bean
  • 可以攔截 Bean 初始化前後的處理
  • 可以在 Bean 屬性注入後和即將銷毀時做一些邏輯處理
  • 解決了循環依賴

其實總結起來它實現的方法就是:載入 Bean 定義、實例化 Bean,很簡單吧

Bean 在 spring 中的完整生命周期,可以自行查看spring 的 BeanFactory 介面,它在最上面的注釋有詳細說明。

但實際的應用場景除了這個主要 Bean 管理外,還有一些消息廣播、國際化、事件監聽等

本文內容

本文想分析下 ApplicationContext 的 refresh 過程,先來個總結性的過程,以便後續一堆枯燥的源碼分析

  • 初始階段:準備工作

  • 第一步,我們先和得到一個 BeanFactory ,這裡初始化的是 DefaultListableBeanFactory

  • 第二步,我們需要配置這個 BeanFactory 像 spel 表達式解析器、Environment 都要配置到工廠裡面,以便後續的處理

  • 第三步,我們允許添加 BeanFactoryProcessor 來修改 BeanFactory 中的 Bean ,常用的子類 BeanDefinitionRegistryPostProcessor 它可以往容器中添加 Bean 定義

    像 @Import、@PropertySource、@ComponentScan、@ImportResource、@Bean methods 還有 tk.mybatis 都是靠它來注入自定義的 Bean 定義 的,不清楚什麼是 bean 定義看我的 上篇文章

  • 第五步,當然是 回調 上面收集到的所有的 BeanFactoryProcessor 了

  • 第六步,回調 所有收集到的 BeanPostProcessor,這些 processor 是已經加到容器中的,如果你看到這裡,不懂什麼是 BeanPostProcessor 請回頭看 上文 的 bean 的生命周期,不清楚回調那就沒辦法了,你還不夠經驗看這篇文章

  • 第七步,國際化初始化(對於主體來說,這算是支線,因為大部分情況下我們都不是寫的國際項目)

  • 第八步,初始化事件廣播器。事件:常見的如頁面載入完成有 onLoad 事件,應用剛啟動有 onLaunch 事件,項目中用得不多,可能是 spring 內部用得多吧

  • 第九步,留一個回調給子類來初始化特別的 bean ,默認空的

  • 第十步,註冊監聽器(所有實現了 ApplicationListener 的類),並把之前的一些事件發布一下

  • 第十一步,基本上是單例實例化,基本上我們項目中的類都是在這個方法中實例化的

  • 結束階段:清理工作

看這個過程的好處

  • 熟悉 IOC 容器初始化的流程,可以把 spring 用得更好
  • 一些像 spel 工具類性質的東西可以直接拿過來用,沒必要引第三方庫或重複造輪子

正文

不會把每一步都細品,只會對一些關鍵的步驟進行解讀,像如何載入 Bean 定義,讀 xml 和讀註解的,讀者可以自行研讀,本文討論幾個關鍵的 processor

我們都知道,Bean 的生命周期中,可以添加 BeanPostProcessor 在 bean 的初始化前和初始化後做一些處理,同樣的在 BeanFactory 也有一個 BeanFactoryPostProcessor 允許你在 BeanFactory 初始化後,修改 BeanFactory。

樣例:

我們看一下 PropertyPlaceholderConfigurer xml 配置時代的產物, 它的繼承結構如下

PropertyResourceConfigurer implements BeanFactoryPostProcessor     |-PlaceholderConfigurerSupport        |-PropertyPlaceholderConfigurer

我們通常會在最開始配置這個

<context:property-placeholder location="classpath:jdbc.properties" />

PropertyPlaceholderConfigurer用於對項目中的 @Value 值進行處理,把值注入進屬性中,下面看下它如何實現的

查看關鍵方法 PropertyResourceConfigurer.postProcessBeanFactory,分為三步

Properties mergedProps = mergeProperties();     // 從 location 中載入屬性    // Convert the merged properties, if necessary.  convertProperties(mergedProps);                 //什麼都沒幹,一個模板方法,可用於密碼加密,自定義屬性轉換    // Let the subclass process the properties.  processProperties(beanFactory, mergedProps);    // 真正處理屬性的地方,解析 spel 表達式 ${}

真正的處理過程使用了訪問者模式在 BeanDefinitionVisitor 中。

springboot 又是怎麼處理的呢,springboot 是固定了配置文件 application.properties ,而不再使用 location 來配置路徑,它使用了 PropertySourcesPlaceholderConfigurer 來解析配置 ,它同樣繼承自 PlaceholderConfigurerSupport 並且重寫了 postProcessBeanFactory 關鍵方法,在最後面使用了

PropertySourcesPropertyResolver 進行了屬性解析。

使用方式可以在 這篇文章 看到

BeanDefinitionRegistryPostProcessor 繼承自 BeanFactoryPostProcessor 它的參數是 BeanDefinitionRegistry 一個 Bean 定義 的註冊器,它可以做些什麼呢 ,查看它的方法,可以對 bean 定義做增刪改查,厲害吧。

registerBeanDefinition  removeBeanDefinition  getBeanDefinition  containsBeanDefinition  getBeanDefinitionNames  getBeanDefinitionCount  isBeanNameInUse

樣例一:

在 ConfigurationClassPostProcessor 中,它實現了 BeanDefinitionRegistryPostProcessor ,它使用 ConfigurationClassParser 來解析項目中配置的 @Configuration 類,然後使用 ConfigurationClassBeanDefinitionReader 把解析到的類載入到 Bean 定義

具體細節查看 這篇文章

樣例二:

在 tkmybatis 的 MapperScannerConfigurer 中,也實現了 BeanDefinitionRegistryPostProcessor ,它使用 ClassPathMapperScanner 來掃描 Mapper 類,添加進容器

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner

劃重點

其實總結來說,refresh 的過程只是給你提供了一個大致的執行框架,具體的處理還是在一些 processor,listener 中,網上有大部分源碼解讀都拿這個 refresh 過程做大篇幅的講解,其實沒多大必要,就像 servlet 的生命周期一樣,了解其執行過程,但剩下的處理都是子類去具現化的。

分享一篇說得不錯的文章

BeanPostProcessor和BeanFactoryPostProcessor淺析以及在spring初始化中回調

一點小推廣

創作不易,希望可以支援下我的開源軟體,及我的小工具,歡迎來 gitee 點星,fork ,提 bug 。

Excel 通用導入導出,支援 Excel 公式
部落格地址:https://blog.csdn.net/sanri1993/article/details/100601578
gitee:https://gitee.com/sanri/sanri-excel-poi

使用模板程式碼 ,從資料庫生成程式碼 ,及一些項目中經常可以用到的小工具
部落格地址:https://blog.csdn.net/sanri1993/article/details/98664034
gitee:https://gitee.com/sanri/sanri-tools-maven

Tags: