在上一篇文章中,深刻分析和學習了 BeanFactoryPostProcessor
,主體是 BeanFactory
的後處理器,此次來學習主體是 Bean
的後處理器:BeanPostProcessor
。php
定義:它也是 Spring
對外提供的接口,用來給用戶擴展自定義的功能。執行的時機在 bean
實例化階段先後java
本篇思路:git
BeanPostProcessor
定義與 BeanFactoryPostProcessor
不一樣的是,BeanFactoryPostProcessor
的註冊和執行都在同一個方法內,而 BeanPostProcessor
分開兩個方法,分爲註冊和調用兩個步驟。github
常規的 BeanFactory
中是沒有實現後處理器的自動註冊,因此在調用的時候沒有進行手動註冊是沒法使用的,但在 ApplicationContext
中添加了自動註冊功能(在這個 registerBeanPostProcessors
方法中),最後在 bean
實例化時執行 BeanPostProcessor
對應的方法。spring
本次主要介紹 BeanPostProcessor
,同時也會將剩下的 context
擴展功能一塊兒學習~設計模式
通過上一篇文章的學習,應該對 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
,用來測試打印時間和順序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
。
因此咱們重載的兩個接口按照先後順序打印出來了~
上面介紹了使用例子,應該不難理解,接着來看下源碼註冊的方法:
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
相似就不重複多講解了,這段代碼的邏輯挺清晰的~
結束兩個擴展功能,BeanFactoryPostProcessor
和 BeanPostProcessor
的學習使用後,還有其它的擴展功能沒學習到,在一開始基礎機構篇就提到剩下的方法:
這這些擴展功能中,我的感受事件傳播器、監聽器和發送廣播事件這三個會用得比較多,因此下面的內容會花比較大篇幅講這三個擴展。
根據書中的內容介紹,這個消息資源 messageSource
是跟 Spring
國際化相關。
例如中美之間的中英文差異,在不一樣地區顯示不一樣的資源。對於有國際化需求的系統,要爲每種提供一套相應的資源文件,並以規範化命名的形式保存在特定的目錄中,由系統自動根據客戶端的語言或者配置選擇合適的資源文件。
舉個🌰: 定義了兩個資源文件,簡單配置以下
因此能夠經過 Applicationcontext.getMessage()
方法訪問國際化信息,在不一樣的環境中獲取對應的數據。
因爲我的感受這種配置相關的,能夠經過 profile
切換來實現,因此沒有去細看和使用,具體實現和使用請感興趣的同窗們深刻了解吧。
事件傳播器的使用很像咱們設計模式中的觀察者模式,被觀察者變更後通知觀察者進行相應的邏輯處理。
在瞭解 Spring
如何初始化事件傳播器以前,來看下 Spring
監聽的簡單用法。
新建一個類,繼承於 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;
}
}
複製代碼
新建一個類,引用 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
是如何實現消息監聽的功能。
從源碼中分析,發現主要是下面三個步驟:
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
列表中,等待以後調用。
廣播器和監聽器都準備好了,剩下的就是發送事件,通知監聽器作相應的處理:
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