一個小小的里程碑
首先感謝能看到本文的朋友,感謝你的一路陪伴。
如果每篇都認真看的話,會發現本系列以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:SpringBoot和Spring到底有沒有本質的不同?
品Spring:SpringBoot輕松取勝bean定義注冊的“第一階段”
品Spring:SpringBoot發起bean定義注冊的“二次攻堅戰”
品Spring:注解之王@Configuration和它的一眾“小弟們”
>>> 熱門文章集錦 <<<
爸爸又給Spring MVC生了個弟弟叫Spring WebFlux
【面試】吃透了這些Redis知識點,面試官一定覺得你很NB(干貨 | 建議珍藏)
【面試】如果你這樣回答“什么是線程安全”,面試官都會對你刮目相看(建議珍藏)
【面試】迄今為止把同步/異步/阻塞/非阻塞/BIO/NIO/AIO講的這么清楚的好文章(快快珍藏)
【面試】一篇文章幫你徹底搞清楚“I/O多路復用”和“異步I/O”的前世今生(深度好文,建議珍藏)
作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號和知識星球的二維碼,歡迎關注!