Spring執行流程圖如下:
如果圖片顯示不清楚可以訪問如下連結查看高清大圖:
Spring執行流程圖
這個流程圖會隨著我們的學習不斷的變得越來越詳細,也會越來越複雜,希望在這個過程中我們都能朝著精通Spring的目標不斷前進!
我們已經知道了Spring中的第一行代碼其實就是創建了一個AnnotatedBeanDefinitionReader對象,這個對象的主要作用就是註冊bd(BeanDefinition)到容器中。並且在創建這個對象的過程中,Spring還為容器註冊了開天闢地的幾個bd,包括ConfigurationClassPostProcessor,AutowiredAnnotationBeanPostProcessor等等。
this.scanner = new ClassPathBeanDefinitionScanner(this);
上面流程圖上已經標註。
只是簡單地創建了一個ClassPathBeanDefinitionScanner對象。那麼這個ClassPathBeanDefinitionScanner有什麼作用呢?從名字上來看好像就是這個對象來完成Spring中的掃描的,真的是這樣嗎?希望同學們能帶著這兩個問題往下看
ClassPathBeanDefinitionScanner源碼分析
這個類名直譯過來就是:類路徑下的BeanDefinition的掃描器,所以我們就直接關注其掃描相關的方法,就是其中的doScan方法。其代碼如下:
上面這段代碼主要做了四件事
通過findCandidateComponents方法完成掃描判斷掃描出來的bd是否是一個AbstractBeanDefinition,如果是的話執行postProcessBeanDefinition方法判斷掃描出來的bd是否是一個AnnotatedBeanDefinition,如果是的話執行processCommonDefinitionAnnotations方法檢查容器中是否已經有這個bd了,如果有就不進行註冊了接下來我們就一步步分析這個方法,搞明白ClassPathBeanDefinitionScanner到底能起到什麼作用
1、通過findCandidateComponents方法完成掃描
findCandidateComponents方法源碼如下:
addCandidateComponentsFromIndex不用過多關注這個方法。正常情況下Spring都是採用掃描classpath下的class文件來完成掃描,但是雖然基於classpath掃描速度非常快,但通過在編譯時創建候選靜態列表,可以提高大型應用程式的啟動性能。在這種模式下,應用程式的所有模塊都必須使用這種機制,因為當 ApplicationContext檢測到這樣的索引時,它將自動使用它而不是掃描類路徑。要生成索引,只需向包含組件掃描指令目標組件的每個模塊添加附加依賴項即可
scanCandidateComponents(basePackage)正常情況下我們的應用都是通過這個方法完成掃描的,其代碼如下:
從上圖中可以看出,java class + configuration metadata 最終會轉換為一個BenaDefinition,結合我們上面的代碼分析可以知道,java class + configuration metadata實際上就是一個MetadataReader對象,而轉換成一個BenaDefinition則是指通過這個MetadataReader對象創建一個ScannedGenericBeanDefinition。
2、執行postProcessBeanDefinition方法
可以看出,postProcessBeanDefinition方法最主要的功能就是給掃描出來的bd設置默認值,進一步填充bd中的屬性
3、執行processCommonDefinitionAnnotations方法
這句代碼將進一步解析class上的註解信息,Spring在創建這個abd的信息時候就已經將當前的class放入其中了,所有這行代碼主要做的就是通過class對象獲取到上面的註解(包括@Lazy,@Primary,@DependsOn註解等等),然後將得到註解中對應的配置信息並放入到bd中的屬性中
4、註冊BeanDefinition
通過上面的分析,我們已經知道了ClassPathBeanDefinitionScanner的作用,毋庸置疑,Spring肯定是通過這個類來完成掃描的,但是問題是,Spring是通過第二步創建的這個對象來完成掃描的嗎?我們再來看看這個ClassPathBeanDefinitionScanner的創建過程:
在這個ClassPathBeanDefinitionScanner的創建過程中我們全程無法幹涉,不能對這個ClassPathBeanDefinitionScanner進行任何配置。而我們在配置類上明明是可以對掃描的規則進行配置的,例如:
@ComponentScan(value = "com.spring.study.springfx.aop.service", useDefaultFilters = true, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {IndexService.class}))
所以Spring中肯定不是使用在這裡創建的這個ClassPathBeanDefinitionScanner對象。
實際上真正完成掃描的時機是在我們流程圖中的3-5-1步。完成掃描這個功能的類就是我們在上篇文章中所提到的ConfigurationClassPostProcessor。接下來我們就通過這個類,看看Spring到底是如何完成的掃描,這也是本文重點想要說明的問題
Spring是怎麼解析配置類的?
1、解析時機分析
解析前Spring做了什麼?
註冊配置類
在分析掃描時機之前我們先回顧下之前的代碼,整個程序的入口如下:
其中在this()空參構造中Spring實例化了兩個對象,一個是AnnotatedBeanDefinitionReader,在上篇文章中已經介紹過了,另外一個是ClassPathBeanDefinitionScanner,在前文中也進行了詳細的分析。
在完成這兩個對象的創建後,Spring緊接著就利用第一步中創建的AnnotatedBeanDefinitionReader去將配置類註冊到了容器中。看到這裡不知道大家有沒有一個疑問,既然Spring是直接通過這種方式來註冊配置類,為什麼我們還非要在配置類上添加@Configuration註解呢?按照這個代碼的話,我不在配置類上添加任何註解,也能將配置類註冊到容器中,例如下面這樣:
大家仔細想想我這個問題,不妨帶著這些疑問繼續往下看。
調用refresh方法
在將配置類註冊到容器中後,Spring緊接著又調用了refresh方法,其源碼如下:
大部分的代碼都寫了很詳細的注釋,對於其中兩個比較複雜的方法我們單獨分析prepareBeanFactoryinvokeBeanFactoryPostProcessors
prepareBeanFactory做了什麼?
上面這段代碼整體來說還是非常簡單的,邏輯也很清晰,就是在為beanFactory做一些配置,我們需要注意的是跟後置處理器相關的內容,可以看到在這一步一共註冊了兩個後置處理器
ApplicationContextAwareProcessor,用於執行xxxAware接口中的方法ApplicationListenerDetector,保證監聽器被添加到容器中invokeBeanFactoryPostProcessors做了什麼?
整的來說,它就是將容器中已經註冊的bean工廠的後置處理器按照一定的順序進行執行。
那麼到這一步為止,容器中已經有哪些bean工廠的後置處理器呢?
還記得我們在上篇文章中提到的ConfigurationClassPostProcessor嗎?在創建AnnotatedBeanDefinitionReader的過程中它對應的BeanDefinition就被註冊到容器中了。接下來我們就來分析ConfigurationClassPostProcessor這個類的源碼
ConfigurationClassPostProcessor源碼分析
它實現了BeanDefinitionRegistryPostProcessor,所以首先執行它的postProcessBeanDefinitionRegistry方法,其源碼如下
processConfigBeanDefinitions方法的代碼很長,我們拆分一段段分析,先看第一段
第一段
上面這段代碼有這麼幾個問題:
當前容器中有哪些BeanDefinition如果你看過上篇文章的話應該知道,在創建AnnotatedBeanDefinitionReader對象的時候Spring已經往容器中註冊了5個BeanDefinition,再加上註冊的配置類,那麼此時容器中應該存在6個BeanDefinition,我們可以打個斷點驗證
不出所料,確實是6個
checkConfigurationClassCandidate代碼如下:
第二段
這段代碼核心目的就是為了創建一個ConfigurationClassParser,這個類主要用於後續的配置類的解析。
第三段
2、解析源碼分析
在上面的源碼分析中,我們已經能夠確定了Spring是通過ConfigurationClassParser的parse方法來完成對配置類的解析的。Spring對類的取名可以說是很講究了,ConfigurationClassParser直譯過來就是配置類解析器。接著我們就來看看它的源碼
2.1、parse方法
2.2、processConfigurationClass方法
2.3、doProcessConfigurationClass方法
可以看到,在doProcessConfigurationClass真正完成了對配置類的解析,一共做了下面幾件事
解析配置類中的內部類,看內部類中是否有配置類,如果有進行遞歸處理
處理配置類上的@PropertySources跟@PropertySource註解
處理@ComponentScan,@ComponentScans註解
處理@Import註解
處理@ImportResource註解
處理@Bean註解
處理接口中的default方法
返回父類,讓外部的循環繼續處理當前配置類的父類
我們逐一進行分析
2.4、處理配置類中的內部類
這段代碼非常簡單,限於篇幅原因我這裡就不再專門分析了,就是獲取到當前配置類中的所有內部類,然後遍歷所有的內部類,判斷是否是一個配置類,如果是配置類的話就遞歸進行解析
2.5、處理@PropertySource註解
代碼也非常簡單,根據註解中的信息加載對應的屬性文件然後添加到容器中
2.6、處理@ComponentScan註解
這個段我們就需要看一看了,Spring在這裡完成的掃描,我們直接查看其核心方法,org.springframework.context.annotation.ComponentScanAnnotationParser#parse
看到了吧,第一步就創建了一個ClassPathBeanDefinitionScanner,緊接著通過解析註解,對這個掃描器進行了各種配置,然後調用doScan方法完成了掃描。
2.7、處理@Import註解
2.8、處理@ImportResource註解
代碼也很簡單,在指定的位置加載資源,然後添加到configClass中。一般情況下,我們通過@ImportResource註解導入的就是一個XML配置文件。將這個Resource添加到configClass後,Spring會在後文中解析這個XML配置文件然後將其中的bd註冊到容器中,可以參考org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions方法
2.9、處理@Bean註解
將配置類中所有的被@Bean標註的方法添加到configClass的BeanMethod集合中
2.10、處理接口中的default方法
代碼也很簡單,Java8中接口能定義default方法,這裡就是處理接口中的default方法,看其是否有@Bean標註的方法
到此為止,我們分析完了整個解析的過程。可以發現Spring將所有解析到的配置信息都存儲在了ConfigurationClass類中,但是到目前為止這些存儲的信息都沒有進行使用。那麼Spring是在哪裡使用的這些信息呢?回到我們的第三段代碼,其中有一行代碼如圖所示:
也就是在這裡Spring完成了對解析好的配置類的信息處理。
2.11、加載解析完成的配置信息
這段代碼閱讀起來還是非常簡單的,這裡我就跟大家一起看下BeanMethod的相關代碼,主要是為了讓大家對BeanDefinition的理解能夠更加深入,其源碼如下:
上面這個方法的主要目的就是將@Bean標註的方法解析成BeandDefinition然後註冊到容器中。關於這個方法我們可以對比下之前分析過的org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean方法。對比我們可以發現,這兩個方法最大的不同在於一個是基於Class對象的,而另一個則是基於Method對象的。
正因為如此,所有它們有一個很大的不同點在於BeanDefinition中BeanClasss屬性的設置。可以看到,對於@Bean形式創建的Bean其BeanDefinition中是沒有設置BeanClasss屬性的,但是額外設置了其它的屬性
靜態方法下,設置了BeanClassName以及FactoryMethodName屬性,其中的BeanClassName是靜態方法所在類的類名,FactoryMethodName是靜態方法的方法名實例方法下,設置了FactoryBeanName以及FactoryMethodName屬性,其中FactoryBeanName是實例對應的Bean的名稱,而FactoryMethodName是實例中對應的方法名之所以不用設置BeanClasss屬性是因為,通過指定的靜態方法或者指定的實例中的方法也能唯一確定一個Bean。
除此之外,註冊@Bean形式得到的BeanDefinition時,還進行了一個isOverriddenByExistingDefinition(beanMethod, beanName)方法的判斷,這個方法的主要作用是判斷當前要註冊的bean是否被之前已經存在的Bean覆蓋了。
但是在直接通過AnnotatedBeanDefinitionReader#doRegisterBean方法註冊Bean時是沒有進行這個判斷的,如果存在就直接覆蓋了,而不會用之前的bd來覆蓋現在要註冊的bd。這是為什麼呢?據筆者自己的理解,是因為Spring將Bean也是分成了三六九等的,通過@Bean方式得到的bd可以覆蓋掃描出來的普通bd(ScannedGenericBeanDefinition),但是不能覆蓋配置類,所以當已經存在的bd是一個ScannedGenericBeanDefinition時,那麼直接進行覆蓋,但是當已經存在的bd是一個配置類時,就不能進行覆蓋了,要使用已經存在的bd來覆蓋本次要註冊的bd。
到此為止,我們就完成了Spring中的整個配置類解析、註冊的相關源碼分析,不過還沒完,我們還得解決一個問題,就是為什麼要在配置類上添加@Configuration註解,在之前的源碼分析中我們知道,添加@Configuration註解的作用就是講配置類標誌成了一個full configurationClass,這個的目的是什麼呢?本來是打算一篇文章寫完的,不過實在是太長了,接近6w字,所以還是拆成了兩篇,預知後事如何,請看下文:配置類為什麼要添加@Configuration註解呢?
總結
清晰的知道了執行的流程,我們再來回想下postProcessBeanDefinitionRegistry做了什麼。
碼字不易,對你有幫助的話記得點個讚,關注一波哦,萬分感謝!