品Spring:註解之王@Configuration和它的一眾「小弟們」
- 2019 年 10 月 3 日
- 筆記
其實對Spring的了解達到一定程度後,你就會發現,無論是使用Spring框架開發的應用,還是Spring框架本身的開發都是圍繞著註解構建起來的。
空口無憑,那就說個最普通的例子吧。
在Spring中要啟用一項XXX功能,標準做法就是用@EnableXXX這種“啟用”類型的註解。
那麼這種類型的註解一般都做了什麼呢?分析一下吧。
看過本號文章的人都知道,Spring的核心就是bean定義和bean,如果我想增加某方面的功能,只需寫若干個類,並作為bean定義註冊到容器中即可。
因此“啟用”類型的註解表面上看起來是啟用某項功能,背後其實就是註冊了一系列相關的bean定義到容器中。
大家都非常熟悉的,啟用事務管理功能的註解,@EnableTransactionManagement,如下圖01:
還有啟用定時任務的註解,@EnableScheduling,如下圖02:
還有啟動非同步支援的註解,@EnableAsync,如下圖03:
這類註解其實很多,就先看這三個吧。
相信大家已經發現了一個規律,這三個註解在定義時都使用@Import註解引入了某個類,最終目的當然是為了註冊bean定義。
之前的文章中已經說過多遍,這種方法叫做通過編程的方式(或稱寫程式碼的方式)註冊bean定義。
這種方式非常的靈活,因為@Import註解引入的類其實是兩個著名介面的實現類。
這兩個介面,之前也提到過很多次了,就是:
ImportSelector
ImportBeanDefinitionRegistrar
在實現介面時,可以按照自己關注的因素,來自主可控的(或稱有選擇性的)註冊bean定義,這就是靈活性的原因。
@Import除了可以引入這兩個介面外,還可以引入被@Configuration註解標註的類或沒有被註解標註的普通類。
再次贅述一下這種方式的典型用法:
用於Spring框架自身的開發,以及第三方框架與Spring的整合開發。
熟悉Spring的朋友都應該知道,@EnableXXX註解和@Import註解都必須(直接或間接)和@Configuration註解放到一起才會起作用。
這是Spring規定的,主要是和Spring的源碼實現有關。
所以整體就是,@Configuration類引入了@EnableXXX註解或@Import註解,@EnableXXX註解又引入了@Import註解。
而@Import註解又引入了那兩個介面的實現類以編程方式註冊bean定義,這裡註冊的bean定義同樣還可以是@Configuration類。
@Import註解還可以再次引入@Configuration類。因此,我們成功的從@Configuration類出發後又回到了@Configuration類。
然後可以繼續重複這個動作,一直進行下去。
這就像原子彈爆炸的鏈式反應一樣,從最初的一個引爆點,然後開啟逐個自我引爆的模式,越來越大,一發不可收拾。
所以Spring就是採用這種模式,最初的引爆點必須是一個@Configuration類,自我的引爆模式是@Import註解。
從最初的那一個基點,向容器中引入新的類,再以引入的新類作為基點,再次引入新的類,這種爆炸模型,非常強大。
除了@Import可以引入新類外,@ComponentScan註解也以掃描jar包的方式引入新的類。只不過掃描的方式通常用於用戶程式而非框架開發。
可見Spring程式就是圍繞著註解構建起來的,從最初的一個@Configuration類,配合使用@Import和@ComponentScan,逐漸壯大。
所以最初的這一個@Configuration類,是在應用啟動前,顯式的(或稱特意的)註冊到容器中的。
SpringBoot就是非常標準的這個樣子的。它的主類就是一個@Configuration類,然後再使用@Import和@ComponentScan註解。
看看下面的註解源碼,確實是這樣子的,如下圖0405:
終於可以下結論了:
@Configuration就是註解之王,是老大,其餘的註解圍繞在它周圍,是小弟們。
按照江湖規矩,老大得罩著小弟們,所以負責處理@Configuration註解的類也要負責處理其它的註解。
編程新說注:究竟有哪些註解可以和@Configuration一起使用,在上一篇文章中已寫明。
目前看來,主要是這樣的:
在容器啟動前使用編程的方式可以註冊bean定義,如SpringBoot的主類。
在容器啟動後,@Import和@ComponentScan都可以註冊bean定義,@Bean方法也可以。
雖然它們功能相似,但側重點其實是不同的,當然可以放到一起使用,但必須做到心中有數。
如果使用不當的話,可能會發生“重疊”,如一個類即被引入了,也被掃描了。
而且還可能被引入多次,掃描多次,當然,即使這樣也不一定報錯,因為Spring做了處理。
Spring採用了類似先入為主的做法:
首先是容器啟動前通過編程方式註冊的類。這些類在後面無論是被掃描還是被引入,都不會再被註冊。
其次是如果先通過掃描方式註冊的類,引入時不再註冊。如果先通過引入方式註冊的類,掃描時不再註冊。
最後是如果先通過掃描方式註冊的類,再次掃描時不再註冊。如果先通過引入方式註冊的類,再次引入時不再註冊。
這就像冪等處理一樣,給上層用戶留有較寬的餘地,在最終使用時沒有那麼嚴格。多了一些隨意。
編程新說注:在顯式註冊bean定義時,如果beanName已存在,會拋出異常。
如果這些註解在一起出現的話,註冊bean定義也是有先後順序的:
第一,容器啟動前,註冊的@Configuration類的bean定義。
編程新說注:下面的這些內容都是依附於@Configuration類的。
第二,@ComponentScan註解掃描的bean定義。
第三,使用ImportSelector介面引入的bean定義(非ImportBeanDefinitionRegistrar介面的實現類)。
第四,使用@Import引入的普通類的bean定義。
第五,類的@Bean方法引入的bean定義。
第六,使用@ImportResource註解引入的XML文件里的bean定義。
第七,使用ImportBeanDefinitionRegistrar介面引入的bean定義。
第八,@Configuration類實現的介面里的@Bean方法(默認方法)。
第九,@Configuration類的父類
接著就是把父類當作新的@Configuration類,再次重複第二到第九。
整個bean定義的註冊過程,就是以容器為依託,一遍一遍的執行,直到容器中的bean定義再無增多為止。
下面就描述其中的一遍:
第一,從容器中拿出尚未處理過的所有@Configuration類
第二,從這一批@Configuration類中解析出所有需要被註冊的類
第三,將這一批@Configuration類標記為已處理過
第四,將剛剛解析出來的類進行bean定義註冊,這些類裡面可能還含有@Configuration類。
接下來進行循環執行即可。直到某一次執行完後,容器中的bean定義數量沒有變化,表明已全部處理完畢。
再說一個外部類和內部類的事情,頗有意思:
@Configuration
class Outter {
@Configuration
static class Inner {
}
}
先來看兩個問題:
當外部類上有註解時,分別使用@ComponentScan和@Import作用於外部類,此時註冊的bean定義是誰?
當把外部類上的註解去掉時,分別使用@ComponentScan和@Import作用於外部類,此時註冊的bean定義是誰?
來看看答案吧:
當外部類有註解時,@ComponentScan和@Import都會把Outter和Inner註冊bean定義。
當外部類沒有註解時,@ComponentScan會註冊Inner類,@Import會註冊Outter類。
下面來解釋下:
JVM規範規定,每個位元組碼文件只能包含一個類,因此嵌套類會單獨編譯成一個位元組碼文件。與它的外部類是互相獨立的。
而@ComponentScan註解是按位元組碼文件掃描的,所以此時內部類都會被註冊,即使外部類上沒有註解。
當@Import引入一個類時,不管該類上是否有註解,該類本身一定會被註冊bean定義。所以外部類總是被註冊。
但是,只有外部類上有註解時,才會去處理它的內部類。且內部類上必須也要有註解才行。因為源碼就是這樣寫的。
最後看看SpringBoot應用註冊的前幾個bean定義,如下圖06:
可見第一個註冊的bean定義,就是用來處理標有@Configuration註解的類的。
第六個註冊的是主類的bean定義。
>>> 熱門文章集錦 <<<
爸爸又給Spring MVC生了個弟弟叫Spring WebFlux
【面試】吃透了這些Redis知識點,面試官一定覺得你很NB(乾貨 | 建議珍藏)
【面試】如果你這樣回答“什麼是執行緒安全”,面試官都會對你刮目相看(建議珍藏)
【面試】迄今為止把同步/非同步/阻塞/非阻塞/BIO/NIO/AIO講的這麼清楚的好文章(快快珍藏)
【面試】一篇文章幫你徹底搞清楚“I/O多路復用”和“非同步I/O”的前世今生(深度好文,建議珍藏)
>>> 品Spring系列文章 <<<
品Spring:SpringBoot和Spring到底有沒有本質的不同?
品Spring:SpringBoot輕鬆取勝bean定義註冊的“第一階段”
品Spring:SpringBoot發起bean定義註冊的“二次攻堅戰”
作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號和知識星球的二維碼,歡迎關注!