1. 程式人生 > >品Spring:詳細解說bean後處理器

品Spring:詳細解說bean後處理器

一個小小的里程碑

首先感謝能看到本文的朋友,感謝你的一路陪伴。

如果每篇都認真看的話,會發現本系列以bean定義作為切入點,先是詳細解說了什麼是bean定義,接著又強調了bean定義為什麼如此重要。

然後又講了獲取bean定義詳細資訊的方法,接著又講了bean定義註冊的若干種方式,然後是bean定義註冊方式的實現細節。

最後又以SpringBoot應用為例,從容器啟動前、啟動後分兩個階段解說bean定義是如何進入到容器裡的。

就是bean工廠後處理器配合使用@ComponentScan註解和@Import註解,一起完了所有bean定義的註冊。

當bean定義註冊完畢後,緊接著就是對單例(singleton)bean的例項化。此時容器處在啟動中。


之前面試別人時問過一個問題,為什麼Spring要在容器啟動時就例項化所有單例bean,而不是放到首次使用時?

記憶中沒有人回答到點上,原因很簡單,就是為了提前發現潛在的錯誤。啟動時報錯比執行時報錯好得多。

OK,從現在開始將進入一個新的階段,即bean的例項化,bean的依賴裝配,bean的初始化。

為了能夠深度參與這個過程,使之更加靈活可配,Spring引入了bean後處理器的概念。


五個bean後處理器介面


bean後處理器主要應用於bean的建立過程中的一些操作,如檢測下是否實現了指定介面,或用一個代理包裝下bean例項,把代理返回等等。

首先來看第一個介面,BeanPostProcessor,如下圖01:


可以看到這是一組對稱的方法,一個是BeforeInitialization,一個是AfterInitialization。

一個在初始化前,一個在初始化後。所以首先要搞清楚什麼是初始化?

在Spring中,初始化指的是在bean例項上執行一個特定的方法,該方法就稱為初始化方法。

程式設計新說注:一般情況下,一個類一個初始化方法就夠了,也可以有多個,Spring的原始碼實現是支援的。

那麼如何指定這個初始化方法呢?共有三種方案可選:

1)實現Spring提供的一個介面,InitializingBean,它只有一個方法,就是afterPropertiesSet。

2)使用@Bean註解註冊bean定義時,設定註解的initMethod屬性為bean的一個方法名。


3)使用java的註解@PostConstruct,把它標在bean的一個方法上。

這三種方式指定的方法都是初始化方法,所謂初始化就是呼叫這些方法。

所以這個介面的兩個方法的呼叫位置就是:

bean的例項化-> bean的依賴裝配 -> 介面方法一(初始化前) -> bean的初始化方法 -> 介面方法二(初始化後) -> OK。

我們可以看到,這個介面的切入位置是在bean的依賴已經裝配好之後,似乎有些“晚了”,因為這樣只能參與bean的初始化,有沒有稍靠前的?

當然有了,接著看第二個介面,InstantiationAwareBeanPostProcessor,如下圖02:


介面的名字中有InstantiationAware,說明是“例項化感知”的bean後處理器。即可以參與到bean的例項化過程中。確實比上一個介面提前了。

介面有三個方法,一個是BeforeInstantiation,一個是AfterInstantiation,一個是Properties。光從名字上就能看出個七七八八了。

所以這個介面的三個方法的呼叫位置就是:

bean的例項化準備階段 -> 介面方法一(例項化前)-> bean的例項化 -> 介面方法二(例項化後) -> 介面方法三(定製bean所需的屬性值) -> bean的屬性設定 -> OK。


第一個方法在bean例項化前呼叫,如果返回一個非null物件,則Spring就使用這個物件了,不再進行例項化了。

所以這裡可以返回一個目標bean的代理,來壓制(延遲)目標bean的例項化。

這個方法的引數是bean的型別,因為此時還沒有bean例項呢。

第二個方法在bean例項化後且屬性設定(顯式的屬性設定或依賴的裝配)前呼叫。

這是一個理想的地方用來執行自定義欄位注入,因為此時Spring的自動裝配尚未到來。

通常方法返回true,如果返回false,後續的屬性設定將被跳過。

同時,後面的該介面型別的例項都將不會再在這個bean例項上呼叫。

第三個方法在bean屬性設定前呼叫,可以用來定製即將為bean例項設定的屬性。

方法pvs是傳進來的已有屬性。方法預設返回null。表示不對屬性進行操作。

第四個方法現已經廢除,等它移除後,第三個方法將預設返回pvs。

下面看第三個介面,SmartInstantiationAwareBeanPostProcessor,也是和bean建立相關的,如下圖03:


這個介面也有三個方法:

第一個方法,用來預測最終的bean型別,這是給我們提供一個修改bean型別的機會,方法的引數是原始的bean型別。

如果方法返回null,則不進行預測,按照Spring自己的邏輯走。

第二個方法,用來確定候選的構造方法,給我們一個定製構造方法的機會,方法的引數是原始的bean型別。

如果方法返回null,則不進行指定,按照Spring自己的邏輯去判斷出最適合的構造方法。

第三個方法,用來獲取一個早期bean例項的引用。為什麼說是早期呢?因為bean例項的初始化方法還沒有執行。

程式設計新說注:可以認為此時的bean還處於一種不完善的狀態。

典型的用法是可以用來解決迴圈引用。這個地方可以在目標bean完全初始化之前較早地暴露一個包裝器。


第四個介面,說完了bean的建立,再來看看bean的銷燬,DestructionAwareBeanPostProcessor,如下圖04:


這個介面比較簡單,只有兩個方法:

第一個方法,在bean例項銷燬前會被呼叫,來執行一些定製的銷燬程式碼。

這些銷燬程式碼通常位於一個方法裡,叫做銷燬方法,是與初始化方法對應的。

同樣也有三種方式來指定銷燬方法:

1)實現Spring提供的一個介面,DisposableBean,它只有一個方法destroy。

2)使用@Bean註解註冊bean定義時,設定註解的destroyMethod屬性為bean的一個方法名。

3)使用java的註解@PreDestroy,把它標在bean的一個方法上。

這三種方式指定的都是銷燬方法。如果指定了的話,就在剛剛的介面方法裡呼叫了。

只有被容器完全管理生命週期的bean才會應用,如singleton和scoped的bean例項。

第二個方法,就是決定是否要為bean例項呼叫第一個方法來執行一些銷燬程式碼。

返回true表示需要,false表示不需要呼叫。


因為第五個介面是和bean定義有關係的,所以先來看看bean定義的實現類都有哪些。

有幾個類需要了解一下,如下圖05:


1)AbstractBeanDefinition,是所有bean定義的父類。

2)RootBeanDefinition,是在XML配置時代,註冊bean定義時用的類。

3)ChildBeanDefinition,是在XML配置時代,註冊bean定義時用的類,必須在配置時指定一個父bean定義。

4)GenericBeanDefinition,在註解配置時代,推薦使用的bean定義類,可以在執行時動態指定一個父bean定義,也可以不指定。

5)AnnotatedGenericBeanDefinition,在註解配置時代,通過程式設計方式註冊bean定義時用的類,繼承了GenericBeanDefinition。

6)ScannedGenericBeanDefinition,在註解配置時代,通過掃描jar包中.class檔案的方式註冊bean定義時用的類,繼承了GenericBeanDefinition。

來分析一下,第3必須有一個父bean定義,第4可以有一個父bean定義,第5、6繼承自第4。第1是一個抽象類。

所以只有第2是一個沒有父bean定義且非抽象的類,因此,Spring會先把bean定義轉換為第2。然後再生成bean例項。

因為可能存在父子關係,所以需要合併bean定義。父子關係其實就是一種“繼承”和“重寫”。

子可以繼承父的資訊,也可以重寫父的資訊,同樣也有些資訊不繼承,只使用子自己的。

這種繼承可以是多級的,如A繼承B,B繼承C,C繼承D。

在合併bean定義時,會把A、B、C、D合起來變成一個M,但是ABCD本身不會再被改變。

合成的bean定義M會被快取起來,就是用它來生成bean例項的。

這些基本知識瞭解了之後,接下來看最後一個介面。

第五個介面,MergedBeanDefinitionPostProcessor,如下圖06:


這個介面的主要目的不是用來修改合併後的bean定義的,雖然也可以進行一些修改。

它主要用來進行一些自省操作,如一些檢測,或在處理bean例項之前快取一些相關的元資料。

這些作用都在第一個方法裡實現。第二個方法是一個通知方法,當一個bean定義被重置時呼叫。

這個方法用於清除和受影響的bean相關的任何元資料。

>>> 品Spring系列文章 <<<

 

品Spring:帝國的基石

品Spring:bean定義上梁山

品Spring:實現bean定義時採用的“先進生產力”

品Spring:註解終於“成功上位”

品Spring:能工巧匠們對註解的“加持”

品Spring:SpringBoot和Spring到底有沒有本質的不同?

品Spring:負責bean定義註冊的兩個“排頭兵”

品Spring:SpringBoot輕鬆取勝bean定義註冊的“第一階段”

品Spring:SpringBoot發起bean定義註冊的“二次攻堅戰”

品Spring:註解之王@Configuration和它的一眾“小弟們”

品Spring:bean工廠後處理器的呼叫規則

 

>>> 熱門文章集錦 <<<

 

畢業10年,我有話說

【面試】我是如何面試別人List相關知識的,深度有點長文

我是如何在畢業不久只用1年就升為開發組長的

爸爸又給Spring MVC生了個弟弟叫Spring WebFlux

【面試】我是如何在面試別人Spring事務時“套路”對方的

【面試】Spring事務面試考點吐血整理(建議珍藏)

【面試】我是如何在面試別人Redis相關知識時“軟懟”他的

【面試】吃透了這些Redis知識點,面試官一定覺得你很NB(乾貨 | 建議珍藏)

【面試】如果你這樣回答“什麼是執行緒安全”,面試官都會對你刮目相看(建議珍藏)

【面試】迄今為止把同步/非同步/阻塞/非阻塞/BIO/NIO/AIO講的這麼清楚的好文章(快快珍藏)

【面試】一篇文章幫你徹底搞清楚“I/O多路複用”和“非同步I/O”的前世今生(深度好文,建議珍藏)

【面試】如果把執行緒當作一個人來對待,所有問題都瞬間明白了

Java多執行緒通關———基礎知識挑戰

品Spring:帝國的基石

 

作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號和知識星球的二維碼,歡迎關注!

 

       

&n