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

Spring 提供的強大擴展功能- BeanFactoryPostProcessor 後處理器

前言

結束了前面的基礎結構分析,瞭解到 Spring 是如何識別配置文件和進行解析屬性,最終將 bean 加載到內存中。同時爲了更好得理解 Spring 的擴展功能,咱們先來鞏固一下 beanFactorybean 的概念,而後再分析新內容後處理器 PostProcessorhtml

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

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

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

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


BeanFactoryPostProcessor

是什麼

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

@FunctionalInterface
public interface BeanFactoryPostProcessor {
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
複製代碼

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

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

同時,執行後處理器是有前後順序的概念,咱們能夠經過設置 order 屬性來控制它們的執行次序,前提是 BeanFactoryPostProcessor 實現了 Order 接口。app

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


如何使用

官方例子:PropertyPlaceholderConfigurer

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

像咱們在 xml 文件中配置了屬性值爲 ${max.threads},可以經過它來找到 max.threads 在配置文件對應的值,而後將屬性填充到 bean 中。

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

先來看下它的繼承體系:

property_placeholder_configure_diagram

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

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

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

由於經過在 PropertyPlaceholderConfigurer 的後處理方法 postProcessBeanFactoryBeanFactory 在實例化任何 bean 以前得到配置信息,從而可以正確解析 bean 描述文件中的變量引用

因此經過後處理器,咱們可以對 beanFactory 中的 bean 配置信息在實例化前還有機會進行修改。

題外話:想到以前我遇到全半角空格的配置問題,程序認爲全半角空格不是同一個字符,但肉眼卻很難察覺,因此感受能夠在加載配置信息時,經過自定義一個後處理,在實例化以前,將全角空格轉成半角空格,這樣程序比較時都變成統同樣式。因此後處理器提供的擴展功能可讓咱們對想要處理的 bean 配置信息進行特定修改


使用自定義 BeanFactoryPostProcessor

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

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

<bean id="carPostProcessor" class="context.CarBeanFactoryPostProcessor">
	<property name="obscenties">
		<!--set 屬性-->
		<set>
			<value>奔馳</value>
			<value>特斯拉</value>
		</set>
	</property>
</bean>

<bean id="car" class="base.factory.bean.Car">
	<property name="price" value="10000"/>
	<property name="brand" value="奔馳"/>
</bean>
複製代碼

2. 後處理器 CarBeanFactoryPostProcessor

public class CarBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

	/** * 敏感詞 */
	private Set<String> obscenties;

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		// 從 beanFactory 中獲取 bean 名字列表
		String[] beanNames = beanFactory.getBeanDefinitionNames();
		for (String beanName : beanNames) {
			BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
			StringValueResolver valueResolver = strVal -> {
				if (isObscene(strVal)) return "*****";
				return strVal;
			};
			BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
			// 這一步纔是真正處理 bean 的配置信息
			visitor.visitBeanDefinition(definition);
		}

	}

	/** * 判斷 value 是否在敏感詞列表中 * @param value 值 * @return boolean */
	private boolean isObscene(Object value) {
		String potentialObscenity = value.toString().toUpperCase();
		return this.obscenties.contains(potentialObscenity);
	}
}
複製代碼

3. 啓動

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"));
    }
}
複製代碼

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

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


在哪註冊

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

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

因此前面的 beanFactoryPostProcessors 數組列表,是讓咱們經過硬編碼方法方式,手動添加進去,而後經過 context.refresh() 方法後,再執行硬編碼的後處理器

例以下面這個例子

public class HardCodeBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public 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"));
複製代碼

激活 BeanFactoryPostProcessor

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

org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors

實際上,委派了 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()) 代理進行執行。因爲代碼有點長,因此我畫了一個流程圖,能夠結合流程圖來分析代碼:

流程圖

代碼

public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
	Set<String> processedBeans = new HashSet<>();
	// beanFactory 默認使用的是 DefaultListableBeanFactory,屬於 BeanDefinitionRegistry
	if (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 接口的方法 :postProcessBeanDefinitionRegistry
		invokeBeanDefinitionRegistryPostProcessors(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 的處理主要分兩種狀況:

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

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

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

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

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


總結

本次分析了 beanFactory 的後處理器 BeanFactoryPostProcessor,瞭解了 Spring 給咱們提供的這個擴展接口使用用途和在源碼中如何進行激活執行。

因爲我的技術有限,若是有理解不到位或者錯誤的地方,請留下評論,我會根據朋友們的建議進行修正

由於我也是一邊看書,一邊作筆記,下載了源碼。 代碼和註釋都在裏面,小夥伴們能夠下載我上傳的代碼,親測可運行~

spring-analysis-note 碼雲 Gitee 地址

spring-analysis-note Github 地址


參考資料

  1. Bean 定義
  2. Sping 的 BeanFactory 容器
  3. Spring拓展接口之BeanFactoryPostProcessor,佔位符與敏感信息解密原理
  4. Spring 源碼深度解析 / 郝佳編著. -- 北京 : 人民郵電出版社

傳送門:

相關文章
相關標籤/搜索