Spring 源碼學習(六)擴展功能 上篇

結束了前面的基礎結構分析,瞭解到 Spring 是如何識別配置文件和進行解析屬性,最終將 bean 加載到內存中。

同時為了更好得理解 Spring 的擴展功能,我們先來鞏固一下 beanFactory 和 bean 的概念,然後再分析新內容後處理器 PostProcessor 。

本篇閱讀思路:

  1. 是什麼
  2. 如何使用
  3. Spring 實現邏輯

Table of Contents generated with DocToc

  • BeanFactoryPostProcessor是什麼如何使用官方例子:PropertyPlaceholderConfigurer使用自定義 BeanFactoryPostProcessor在哪註冊激活 BeanFactoryPostProcessor流程圖代碼總結
  • 參考資料

前言

首先我們先將 Spring 想像成一個大容器,然後保存了很多 bean 的信息,根據定義:bean 是一個被實例化,組裝,並通過 Spring IoC 容器所管理的對象,也可以簡單得理解為我們在配置文件配置好元數據,Spring IoC 容器會幫我們對 bean 進行管理,這些對象在使用的時候通過 Spring 取出就能使用。

那麼是誰幫這個 Spring 管理呢,那就是 BeanFactory,粗暴點直譯為 bean 工廠,但其實它才是承擔容器功能的幕後實現者,它是一個接口,提供了獲取 bean 、獲取別名 Alias 、判斷單例、類型是否匹配、是否原型等方法定義,所以需要通過引用,實現具體方法才後才能使用。

回顧完 beanFactory 後,我們再來回顧在前面內容中,看到過很多後處理器 PostProcessor 的代碼影子,分別是 BeanFactoryPostProcessor:主體是 BeanFactory, 和 BeanPostProcessor:主體是 Bean,這兩者都是 Spring 用來為使用者提供的擴展功能之一。

接下來為了更好的分析和了解使用後處理器,實現擴展功能,一起跟蹤源碼學習吧~


BeanFactoryPostProcessor

是什麼

BeanFactoryPostProcessor 是一個接口,在裡面只有一個方法定義:


<code>@FunctionalInterfacepublic interface BeanFactoryPostProcessor {void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;}/<code>

定義:是 Spring 對外提供可擴展的接口,能夠在容器加載了所有 bean 信息(AbstractApplicationContext#obtainFreshBeanFactory 方法)之後,bean 實例化之前執行,用來修改 bean 的定義屬性。

可以看到,方法參數是 ConfigurableListableBeanFactory beanFactory ,說明我們可以通過引用該接口,在方法中實現邏輯,對容器中 bean 的定義(配置元數據)進行處理

同時,執行後處理器是有先後順序的概念,我們可以通過設置 order 屬性來控制它們的執行次序,前提是 BeanFactoryPostProcessor 實現了 Order 接口。

下面一起來看下它的如何使用,以及是如何進行註冊和執行~


如何使用

官方例子:PropertyPlaceholderConfigurer

這個類是 Spring 容器裡自帶的後處理器,是用來替換佔位符,填充屬性到 bean 中。

像我們在 xml 文件中配置了屬性值為 ${max.threads},能夠通過它來找到 max.threads 在配置文件對應的值,然後將屬性填充到 bean 中。

雖然在 Spring 5 中,PropertyPlaceholderConfigurer 已經打上了不建議使用的標誌 @Deprecated,看了文件註釋,提示我們去使用 Environment 來設置屬性,但我覺得這個後處理器的思想是一樣的,所以還是拿它作為例子進行熟悉。

先來看下它的繼承體系:

Spring 源碼學習(六)擴展功能 上篇

忽略它被冷落的下劃線標籤

當 Spring 加載任何實現了 BeanFactoryPostProcessor 接口的 bean 配置時,都會在 bean 工廠載入所有 bean 的配置之後執行 postProcessBeanFactory 方法

可以看到它引用了 BeanFactoryPostProcessor 接口,在 PropertyResourceConfigurer 父類中實現了 postProcessBeanFactory,在方法中依次調用了合併資源 mergedProps 方法,屬性轉換 convertProperties 方法和真正修改 beanFactory 中配置元數據的 processProperties(beanFactory, mergedProps) 方法

因為通過在 PropertyPlaceholderConfigurer 的後處理方法 postProcessBeanFactory,BeanFactory 在實例化任何 bean 之前獲得配置信息,從而能夠正確解析 bean 描述文件中的變量引用

所以通過後處理器,我們能夠對 beanFactory 中的 bean 配置信息在實例化前還有機會進行修改。

題外話:想到之前我遇到全半角空格的配置問題,程序認為全半角空格不是同一個字符,但肉眼卻很難察覺,所以感覺可以在加載配置信息時,通過自定義一個後處理,在實例化之前,將全角空格轉成半角空格,這樣程序比較時都變成統一樣式。

所以後處理器提供的擴展功能可以讓我們對想要處理的 bean 配置信息進行特定修改


使用自定義 BeanFactoryPostProcessor

實現的功能與書中的類似,例如之前西安奔馳汽車維權事件,如果相關網站想要屏蔽這奔馳這兩個字,可以通過後處理器進行替換:

1. 配置文件 factory-post-processor.xml


<code>public class BeanFactoryPostProcessorBootstrap {public static void main(String[] args) {    ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("factory.bean/factory-post-processor.xml");    // 這兩行其實可以不寫,因為在 refresh() 方法中,調用了一個函數將後處理器執行了,具體請往下看~    BeanFactoryPostProcessor beanFactoryPostProcessor = (BeanFactoryPostProcessor) context.getBean("carPostProcessor");    beanFactoryPostProcessor.postProcessBeanFactory(context.getBeanFactory());    // 輸出 :Car{maxSpeed=0, brand='*****', price=10000.0},敏感詞被替換了    System.out.println(context.getBean("car"));    }}/<code>

2. 後處理器 CarBeanFactoryPostProcessor


<code>public class BeanFactoryPostProcessorBootstrap {public static void main(String[] args) {    ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("factory.bean/factory-post-processor.xml");    // 這兩行其實可以不寫,因為在 refresh() 方法中,調用了一個函數將後處理器執行了,具體請往下看~    BeanFactoryPostProcessor beanFactoryPostProcessor = (BeanFactoryPostProcessor) context.getBean("carPostProcessor");    beanFactoryPostProcessor.postProcessBeanFactory(context.getBeanFactory());    // 輸出 :Car{maxSpeed=0, brand='*****', price=10000.0},敏感詞被替換了    System.out.println(context.getBean("car"));    }}/<code>

3. 啟動


<code>public class BeanFactoryPostProcessorBootstrap {public static void main(String[] args) {    ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("factory.bean/factory-post-processor.xml");    // 這兩行其實可以不寫,因為在 refresh() 方法中,調用了一個函數將後處理器執行了,具體請往下看~    BeanFactoryPostProcessor beanFactoryPostProcessor = (BeanFactoryPostProcessor) context.getBean("carPostProcessor");    beanFactoryPostProcessor.postProcessBeanFactory(context.getBeanFactory());    // 輸出 :Car{maxSpeed=0, brand='*****', price=10000.0},敏感詞被替換了    System.out.println(context.getBean("car"));    }}/<code>

通過上面的演示代碼,新增一個自定義實現 BeanFactoryPostProcessor 的後處理器 CarBeanFactoryPostProcessor,在 postProcessBeanFactory 方法中進行邏輯處理,最後通過 visitor.visitBeanDefinition 修改配置信息。

查看輸出結果,能發現寶馬敏感詞已經被屏蔽了,實現了後處理器的邏輯功能~


在哪註冊

按照一般套路,後處理器需要有個地方進行註冊,然後才能進行執行,通過代碼分析,的確在 AbstractApplicationContext 中看到了 beanFactoryPostProcessors 數組列表,但往數組中添加後處理器的方法 addBeanFactoryPostProcessor 只在單元測試包調用了。

這讓我很迷惑它到底是在哪裡進行註冊,直到我看到它的執行方法,原來我們定義的後處理器在 bean 信息加載時就放入註冊表中,然後通過 beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false) 方法獲取後處理器列表遍歷執行。

所以前面的 beanFactoryPostProcessors 數組列表,是讓我們通過硬編碼方法方式,手動添加進去,然後通過 context.refresh() 方法後,再執行硬編碼的後處理器

例如下面這個例子

<code>public class HardCodeBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {System.out.println("Hard Code BeanFactory Post Processor execute time");}}// 硬編碼 後處理器執行時間BeanFactoryPostProcessor hardCodeBeanFactoryPostProcessor = new HardCodeBeanFactoryPostProcessor();context.addBeanFactoryPostProcessor(hardCodeBeanFactoryPostProcessor);// 更新上下文context.refresh();// 輸出://Hard Code BeanFactory Post Processor execute time//Car{maxSpeed=0, brand='*****', price=10000.0}System.out.println(context.getBean("car"));/<code>

激活 BeanFactoryPostProcessor

看完了怎麼使用後,我們來分析下 Spring 是如何識別和執行 BeanFactoryPostProcessor,入口方法:

org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors

實際上,委派了 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()) 代理進行執行。由於代碼有點長,所以我畫了一個流程圖,可以結合流程圖來分析代碼:

流程圖

Spring 源碼學習(六)擴展功能 上篇

代碼

<code>public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<beanfactorypostprocessor> beanFactoryPostProcessors) {Set<string> processedBeans = new HashSet<>();// beanFactory 默認使用的是 DefaultListableBeanFactory,屬於 BeanDefinitionRegistryif (beanFactory instanceof BeanDefinitionRegistry) {BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;// 兩個後處理器列表List<beanfactorypostprocessor> regularPostProcessors = new ArrayList<>();List<beandefinitionregistrypostprocessor> registryProcessors = new ArrayList<>();// 硬編碼註冊的後處理器for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {// 分類處理}List<beandefinitionregistrypostprocessor> currentRegistryProcessors = new ArrayList<>();// 首先,調用實現 priorityOrder 的 beanDefinition 這就是前面提到過的優先級概念,這一步跟下面的優先級不一樣之處,這一步的優先級是帶有權重String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);}}// 對後處理器進行排序sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);currentRegistryProcessors.clear();// Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);}}sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);// 執行 definitionRegistryPostProcessor 接口的方法 :postProcessBeanDefinitionRegistryinvokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);currentRegistryProcessors.clear();// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.最後,調用所有其他後處理器,直到不再出現其他 bean 為止boolean reiterate = true;while (reiterate) {reiterate = false;postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (!processedBeans.contains(ppName)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);reiterate = true;}}sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);currentRegistryProcessors.clear();}// 執行 postProcessBeanFactory 回調方法invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);}else {invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);}// 不要在這裡初始化 factoryBean:我們需要保留所有常規 bean 未初始化,以便讓 bean 工廠後處理程序應用於它們// 註釋 6.4 在這個步驟中,我們自定義的 carBeanFactoryPostProcessor 才真正註冊並執行String[] postProcessorNames =beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);.    // 跳過分類的邏輯// 首先執行的是帶有權重順序的後處理器sortPostProcessors(priorityOrderedPostProcessors, beanFactory);invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);// 下一步執行普通順序的後處理器List<beanfactorypostprocessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());for (String postProcessorName : orderedPostProcessorNames) {orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));}sortPostProcessors(orderedPostProcessors, beanFactory);invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);// 最後執行的是普通的後處理器List<beanfactorypostprocessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());for (String postProcessorName : nonOrderedPostProcessorNames) {nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));}invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);// 清除緩存的合併bean定義,因為後處理程序可能已經修改了原始元數據,例如替換值中的佔位符beanFactory.clearMetadataCache();}/<beanfactorypostprocessor>/<beanfactorypostprocessor>/<beandefinitionregistrypostprocessor>/<beandefinitionregistrypostprocessor>/<beanfactorypostprocessor>/<string>/<beanfactorypostprocessor>/<code>

從上面貼的代碼中能夠看到,對於 beanFactoryPostProcessor 的處理主要分兩種情況:

  • beanFactory 是 BeanDefinitionRegistry 類型:需要特殊處理
  • beanFactory 不是 BeanDefinitionRegistry 類型:進行普通處理

對於每種情況都需要考慮硬編碼注入註冊的後處理器(上面已經提到如何進行硬編碼)以及通過配置注入的後處理器

對於 BeanDefinitionRegistry 類型的處理器的處理主要包括以下內容:

  1. 處理硬編碼註冊的後處理器
  2. 記錄後處理器主要使用以下三個 ListregistryPostProcessors:記錄通過硬編碼方式註冊的 BeanDefinitionRegistryPostProcessorregularPostProcessors:記錄通過硬編碼方式註冊的 BeanFactoryPostProcessorregitstryPostProcessorBeans:記錄通過配置方式註冊的 BeanDefinitionRegistryPostProcessor
  3. 對於以上後處理器列表,統一調用 BeanFactoryPostProcessor 的 postProcessBeanFactory 方法
  4. 對 beanFactoryPostProcessors 中非 BeanDefinitionRegistryPostProcessor 類型的後處理器進行統一的 postProcessBeanFactory 方法
  5. 普通 beanFactory 處理:其實在這一步中,就是忽略了 BeanDefinitionRegistryPostProcessor 類型,對 BeanFactoryPostProcessor 進行直接處理。

流程圖中描述了整體調用鏈路,具體調用方法在代碼中的註釋也描述出來了,所以結合起來看應該能夠理解整體流程~


分享到:


相關文章: