Spring 源碼學習(七) 擴展功能 下篇-BeanPostProcessor

在上一篇文章中,深刻分析和學習了 BeanFactoryPostProcessor ,主體是 BeanFactory 的後處理器,此次來學習主體是 Bean 的後處理器:BeanPostProcessorphp

定義:它也是 Spring 對外提供的接口,用來給用戶擴展自定義的功能。執行的時機在 bean 實例化階段先後java

本篇思路:git

  1. BeanPostProcessor 定義
  2. 如何使用
  3. 代碼實現分析
  4. 介紹剩餘的擴展功能

前言

BeanFactoryPostProcessor 不一樣的是,BeanFactoryPostProcessor 的註冊和執行都在同一個方法內,而 BeanPostProcessor 分開兩個方法,分爲註冊調用兩個步驟。github

常規的 BeanFactory 中是沒有實現後處理器的自動註冊,因此在調用的時候沒有進行手動註冊是沒法使用的,但在 ApplicationContext 中添加了自動註冊功能(在這個 registerBeanPostProcessors 方法中),最後在 bean 實例化時執行 BeanPostProcessor 對應的方法。spring

本次主要介紹 BeanPostProcessor,同時也會將剩下的 context 擴展功能一塊兒學習~設計模式


BeanPostProcessor

通過上一篇文章的學習,應該對 bean 的後處理理解起來更順利,下面直奔主題,來看下它是如何使用和結合源碼分析數組


如何使用

新建一個 bean 後處理器

這個後處理器須要引用 InstantiationAwareBeanPostProcessor 接口(實際繼承自 BeanPostProcessor),而後重載如下兩個方法:bash

public class CarBeanPostProcessor implements InstantiationAwareBeanPostProcessor {

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		// 這裏沒有區分 bean 類型,只是用來測試打印的順序和時間
		System.out.println("Bean name : " + beanName + ", before Initialization, time : " + System.currentTimeMillis());
		return null;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("Bean name : " + beanName + ", after Initialization, time : " + System.currentTimeMillis());
		return null;
	}
}
複製代碼

在配置文件中註冊 bean-post-processor.xml

在配置文件配置咱們寫的自定義後處理器和兩個普通 bean,用來測試打印時間和順序mvc

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	<!-- beanPostProcessor -->
	<bean id="carPostProcessor" class="context.bean.CarBeanPostProcessor"/>

	<!--用如下兩個 bean 進行測試打印時間和順序-->
	<bean id="car" class="base.factory.bean.Car">
		<property name="price" value="10000"/>
		<property name="brand" value="奔馳"/>
	</bean>

	<bean id="book" class="domain.ComplexBook"/>

</beans>
複製代碼

啓動代碼和打印結果

public class CarBeanPostProcessorBootstrap {

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("factory.bean/bean-post-processor.xml");
		Car car = (Car) context.getBean("car");
		ComplexBook book = (ComplexBook) context.getBean("book");
		System.out.println(car);
		System.out.println(book);
	}
}
複製代碼

輸出:app

Bean name : car, before Initialization, time : 1560772863996
Bean name : car, after Initialization, time : 1560772863996
Bean name : book, before Initialization, time : 1560772863999
Bean name : book, after Initialization, time : 1560772863999
Car{maxSpeed=0, brand='奔馳', price=10000.0}
domain.ComplexBook@77be656f
複製代碼

從輸出接口看出,打印順序是先框架內部,再到應用層,框架內部中,在順序實例化每一個 bean 時,前面也提到執行時機:先執行 postProcessBeforeInitialization 方法,而後實例化 bean 後,執行 postProcessAfterInitialization

因此咱們重載的兩個接口按照先後順序打印出來了~


註冊 BeanPostProcessor

上面介紹了使用例子,應該不難理解,接着來看下源碼註冊的方法:

org.springframework.context.support.AbstractApplicationContext#registerBeanPostProcessors

實際委託給了 PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);

public static void registerBeanPostProcessors( ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
		// 註釋 7.2 從註冊表中取出 class 類型爲 BeanPostProcessor 的 bean 名稱列表
		String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

		int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
		beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
		// 將帶有 權限順序、順序和其他的 beanPostProcessor 分開
		List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
		// 類型是 MergedBeanDefinitionPostProcessor
		List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
		List<String> orderedPostProcessorNames = new ArrayList<>();
		List<String> nonOrderedPostProcessorNames = new ArrayList<>();
		for (String ppName : postProcessorNames) {
            // 分類,添加到對應數組中
			...
		}
		// 首先,註冊實現了 PriorityOrdered 接口的 bean 後處理器
		sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
		registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);
		// 下一步,註冊實現了 Ordered 接口的 bean 後處理器
		List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
		for (String ppName : orderedPostProcessorNames) {
			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
			orderedPostProcessors.add(pp);
			if (pp instanceof MergedBeanDefinitionPostProcessor) {
				internalPostProcessors.add(pp);
			}
		}
		sortPostProcessors(orderedPostProcessors, beanFactory);
		registerBeanPostProcessors(beanFactory, orderedPostProcessors);
		// 如今,註冊常規 bean 後處理器,其實就是不帶順序
		List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
		for (String ppName : nonOrderedPostProcessorNames) {
			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
			nonOrderedPostProcessors.add(pp);
			if (pp instanceof MergedBeanDefinitionPostProcessor) {
				internalPostProcessors.add(pp);
			}
		}
		registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
		// 最後,從新註冊 MergedBeanDefinitionPostProcessor 類型的後處理器
		// 看起來是重複註冊了,可是每次註冊調用的底層方法都會先移除已存在的 beanPostProcessor,而後再加進去,最後仍是保存惟一
		sortPostProcessors(internalPostProcessors, beanFactory);
		registerBeanPostProcessors(beanFactory, internalPostProcessors);
		// 添加 ApplicationContext 探測器
		beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
	}
複製代碼

跟以前的 BeanFactoryPostProcessor 處理是否是很類似,也是進行分類,將帶有權重順序、順序和普通 BeanPostProcessor 添加到對應的列表後,而後排序,統一註冊到 beanPostProcessors 列表末尾。

BeanPostProcessor 與以前的 BeanFactoryPostProcessor 進行對比後發現,少了硬編碼註冊的代碼,只處理了配置文件方式的註冊 bean。經過書中闡釋,對少了硬編碼的處理有些理解:

對於 BeanFactoryPostProcessor 的處理,在一個方法內實現了註冊和實現,因此須要載入配置中的定義,並進行激活;而對於 BeanPostProcessor 並不須要立刻調用,硬編碼的方式實現的功能是將後處理器提取並調用,對於 BeanPostProcessor,註冊階段不須要調用,因此沒有考慮處理硬編碼,在這裏只須要將配置文件的 BeanPostProcessor 提取出來並註冊進入 beanFactory 就能夠了。

並且我在測試過程,想在應用代碼中進行硬編碼註冊,發現因爲 ClassPathXmlApplicationContext 最後一個方法是實例化非延遲加載的 bean,在上下文建立好時,BeanPostProcessor 就已經執行完成了,因而硬編碼註冊的後處理器沒法執行,只能經過設定延遲加載或者在配置文件配置中進行註冊,或者其它 BeanFactory 能支持硬編碼。

剩下順序 Order 類型的後處理器註冊 BeanFactoryPostProcessor 相似就不重複多講解了,這段代碼的邏輯挺清晰的~


小結

結束兩個擴展功能,BeanFactoryPostProcessorBeanPostProcessor 的學習使用後,還有其它的擴展功能沒學習到,在一開始基礎機構篇就提到剩下的方法:

這這些擴展功能中,我的感受事件傳播器、監聽器和發送廣播事件這三個會用得比較多,因此下面的內容會花比較大篇幅講這三個擴展。


初始化消息資源

根據書中的內容介紹,這個消息資源 messageSource 是跟 Spring 國際化相關。

例如中美之間的中英文差異,在不一樣地區顯示不一樣的資源。對於有國際化需求的系統,要爲每種提供一套相應的資源文件,並以規範化命名的形式保存在特定的目錄中,由系統自動根據客戶端的語言或者配置選擇合適的資源文件。

舉個🌰: 定義了兩個資源文件,簡單配置以下

  • 中文地區: test=測試
  • 英文地區: test=test

因此能夠經過 Applicationcontext.getMessage() 方法訪問國際化信息,在不一樣的環境中獲取對應的數據。

因爲我的感受這種配置相關的,能夠經過 profile 切換來實現,因此沒有去細看和使用,具體實現和使用請感興趣的同窗們深刻了解吧。


事件監聽

事件傳播器的使用很像咱們設計模式中的觀察者模式,被觀察者變更後通知觀察者進行相應的邏輯處理。

在瞭解 Spring 如何初始化事件傳播器以前,來看下 Spring 監聽的簡單用法。

定義監聽事件 Event

新建一個類,繼承於 ApplicationEvent,而且須要在構造方法中調用父類的構造函數 supre(source)

public class CarEvent extends ApplicationEvent {

	/** * 自定義一個消息 */
	private String msg;

	public CarEvent(Object source) {
		super(source);
	}

	public CarEvent(Object source, String msg) {
		super(source);
		this.msg = msg;
	}
}
複製代碼

定義監聽器 Listener

新建一個類,引用 ApplicationListener 接口,而後重載 onApplicationEvent 方法:

public class CarEventListener implements ApplicationListener {
	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof CarEvent) {
			CarEvent carEvent = (CarEvent) event;
			System.out.println("source : " + event.getSource() + ", custom message : " + carEvent.getMsg());
		}
	}
}
複製代碼

因爲 Spring 的消息監聽器不像 kafka 等主流 MQ 能夠指定發送隊列或者監聽主題,只要發送消息後,全部註冊的監聽器都會收到消息進行處理,因此這邊加了一個判斷,若是是我業務上須要的消息,纔會進行處理。


配置文件

<bean id="testListener" class="context.event.CarEventListener"/>
複製代碼

將剛纔寫的監聽器註冊到 Spring 容器中


測試代碼

public class EventBootstrap {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("factory.bean/bean-post-processor.xml");
		// 第一個參數是來源,第二個參數是自定義
		CarEvent carEvent = new CarEvent("hello",  "world");
		context.publishEvent(carEvent);
		// 消息發送以後,打印如下內容
		// source : hello, custom message : world
	}
}
複製代碼

因爲在配置文件中註冊了監聽器,而後在啓動代碼彙總初始化了監聽事件,最終經過 context 發送消息,發現輸出結果與預想的一致。

這種觀察者模式實現很經典,使用起來也很簡單,下面來結合源碼分析一下 Spring 是如何實現消息監聽的功能。


消息監聽代碼分析

從源碼中分析,發現主要是下面三個步驟:

初始化 ApplicationEvenMulticaster

protected void initApplicationEventMulticaster() {
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	// 若有有本身註冊class Name 是 applicationEventMulticaster,使用自定義廣播器
	if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
		this.applicationEventMulticaster =
				beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
		}
	}
	else {
		// 沒有自定義,使用默認的事件廣播器
		this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
		beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
	}
}
複製代碼

廣播器的做用是用來廣播消息,在默認的廣播器 SimpleApplicationEventMulticaster 類中發現了這個方法 multicastEvent

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	Executor executor = getTaskExecutor();
	// 遍歷註冊的消息監聽器
	for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		if (executor != null) {
			executor.execute(() -> invokeListener(listener, event));
		}
		else {
			invokeListener(listener, event);
		}
	}
}
複製代碼

能夠看到,在廣播事件時,會遍歷全部註冊的監聽器進行調用 invokeListener 方法,底層調用的是監聽器重載的 listener.onApplicationEvent(event),因此再次強調一次,若是使用 Spring 自帶的事件監聽,請在業務處理方判斷事件來源,避免處理錯誤。


註冊監聽器

在上一步中,已經初始化好了廣播器,因此下一步來看下,監聽器的註冊流程,入口方法以下:

org.springframework.context.support.AbstractApplicationContext#registerListeners

protected void registerListeners() {
	// 這裏是硬編碼註冊的監聽器
	for (ApplicationListener<?> listener : getApplicationListeners()) {
		getApplicationEventMulticaster().addApplicationListener(listener);
	}
	// 不要在這裏初始化 factoryBean : 咱們須要保留全部常規 bean 未初始化,以便讓後處理程序應用於它們!
	// 這一步是配置文件中註冊的監聽器
	String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
	for (String listenerBeanName : listenerBeanNames) {
		getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
	}

	// 發佈早期的應用程序事件,如今咱們終於有了一個多播器=-=
	Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
	this.earlyApplicationEvents = null;
	if (earlyEventsToProcess != null) {
		for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
			getApplicationEventMulticaster().multicastEvent(earlyEvent);
		}
	}
}
複製代碼

這一個方法代碼很少,也沒啥嵌套功能,按照註釋順序將流程梳理了一遍,將咱們註冊的監聽器加入到 applicationEventMulticaster 列表中,等待以後調用。


publishEvent

廣播器和監聽器都準備好了,剩下的就是發送事件,通知監聽器作相應的處理:

org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)

核心是這行代碼:

getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
複製代碼

經過獲取事件廣播器,調用 multicastEvent 方法,進行廣播事件,這一步前面也介紹過了,再也不細說。


總結

此次學習,省略了書中的一些內容,有關屬性編輯器、SPEL 語言和初始化非延遲加載等內容,請感興趣的同窗繼續深刻了解~

咱們也能從 Spring 提供的這些擴展功能中學習到,經過預留後處理器,能夠在 bean 實例化以前修改配置信息,或者作其餘的自定義操做,例如替換佔位符、過濾敏感信息等;

也能夠經過廣播事件,定義事件和監聽器,在監聽器中實現業務邏輯,因爲不是直接調用監聽器,而是經過事件廣播器進行中轉,達到了代碼解耦的效果。

因此在以後的代碼設計和編寫中,在總體設計上,有必要的話,考慮在更高的抽象層要預留擴展功能,而後讓子類重載或者實現,實現擴展的功能。


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

代碼和註釋都在裏面,小夥伴們能夠下載我上傳的代碼,親測可運行~

Gitee 地址:https://gitee.com/vip-augus/spring-analysis-note.git

Github 地址:https://github.com/Vip-Augus/spring-analysis-note


參考資料

  1. Spring 源碼深度解析 / 郝佳編著. -- 北京 : 人民郵電出版社

傳送門:

相關文章
相關標籤/搜索