品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