SpringBoot項目啓動過程源碼終於整體捋了一遍(五)

這篇繼續看初始化SpringApplication的時候如何設置初始化器的,上篇看到了getSpringFactoriesInstances()方法,先貼出來:

上篇擼了第一行,理了java的類加載相關,繼續往下看:

 註釋說使用名稱並確保唯一,以防止重複,這些names被放進了Set集合,原來大佬們也喜歡用Set防止重複啊,繼續看這些names怎麼來的,這個SpringFactoriesLoader.loadFactoryNames()方法:

factoryClass就是一開始的參數ApplicationContextInitializer.class,即初始化器的class, 方法一開始是拿到了類名,注意接下來的方法loadSpringFactories()的返回類型是Map,後面也是調用了Map的getOrDefault()方法,看來是要在這個Map中找到key是這個ApplicationContextInitializer類名的value,找不到就是空集合,這個getOrDefault()可以學起來,有的時候可以省去非空判斷。看來這個loadSpringFactories()方法單憑一個類加載器加載了一股腦的東西放進了Map裏,其中就有初始化器ApplicationContextInitializer,那它從哪裏加載的呢,都加載了些什麼呢,那就看看下面這個loadSpringFactories()方法。

方法第一行調用了cache.get(),看上去像是個緩存,果然這個cache也是個Map,還是個ConcurrentReferenceHashMap:

這裏要注意ConcurrentReferenceHashMap裏面存放的對象是軟引用,java有強、弱、軟、虛四種引用,其中軟引用對象在JVM內存不足時就會直接去回收它們,所以它們不會造成內存溢出,同理在Map中也不會造成內存泄漏,這裏可以學起來,當然業務中的數據大部分場景還是需要強引用的。關於這四種引用這裏就不多介紹了。這個方法最後果然也有這行:

好吧,看來這個單憑一個類加載器加載出來的result還會被放進緩存,key居然是憑藉的類加載器。想想也對,一個類加載器辛辛苦苦把一股腦的東西全加載出來了,取用的時候只就一個一個的取,這當然得緩存了。接下來就要看看這個result到底是什麼,它從哪裏來。接下來是這段:

Enumeration是一個枚舉的集合,用起來有點像迭代器,還沒有迭代器清晰,這裏就理解成一個url的集合就行了,待會要遍歷。後面是一個三目運算符,類加載器不爲空的時候去classLoader.getResources()尋找靜態資源文件路徑url,參數路徑看一下是什麼:

 注意這裏的路徑是不以"/"開頭的,還有需要注意這裏是getResources()而不是getResource(),這兩個方法是有區別的,前者返回所有匹配的資源路徑urL,而後者只返回匹配的第一個。而且這兩個方法參數路徑都不能以"/"開頭,因爲他們是去classPath路徑下去查找,所以必須是相對路徑。那源碼中的路徑拿到的其實就是所有jar包中的spring.factories文件,舉一個例子:

這是在IDEA編輯器中,類加載器即AppClassLoader,這裏的jar包文件和META-INF也都是在classPath下,如果是打成jar包,別忘了之前說的springBoot自己有一個類加載器LaunchedURLClassLoader,這裏面重寫了findSource方法,儘管fat jar裏面的目錄結構不同,還是可以找到這些spring.factories文件。

繼續看上圖中的loadSpringFactories()中拿到這些spring.factories文件路徑後做什麼:

可以看到用了Properties類,說明是去讀取文件內容了,而且產物result的key是className剛說過了,value就是一個String。隨便看一個 spring.factories文件,裏面長這樣的:

原來這個spring.factories文件裏面的配置可以看成一個一個的鍵值對,而且值是一個類路徑,那看來result的值就是這裏面給每個鍵配置的類路徑了。

其實到這裏已經基本理清了SpringApplication是如何設置初始化器的,過程就是先通過類加載器找到一堆spring.factories資源文件,然後把這些spring.factories文件裏面的配置都讀取出來,然後再找一個鍵叫ApplicationContextInitializer.class的看看給他配置的類路徑是什麼,估計後面就是加載這個類了吧,這一步後面會看到。

這裏就可以思考這個spring.factories文件對於SpringBoot的重要性,我們常把SpringBoot的開箱即用特性掛載嘴邊,自動配置啊,約定大於配置啊包括starter啊什麼的,想用什麼功能引個starter就行了,關鍵就在這個spring.factories文件。

舉個例子,比如我們之前用Spring MVC的時候需要配置前端控制器、處理器適配器以及處理器映射器什麼的一大堆,而這些都可以在對應starter的spring.factories文件中找到對應的AutoConfiguration自動配置類,再舉個例子,我一眼就看到了這個:

不然我們在SpringBoot中訪問數據庫就只要在yml文件裏寫一個配置什麼也不用做是爲什麼,難道以前的步驟都消失了?當然不是,只不過是SpringBoot悄悄的做掉了。之所以用spring.factories文件的方式也是考慮到可擴展,你可以自己寫starter啊,用自己的starter用自己的方式去做這些事。

好了好了又扯遠了,繼續看這篇開頭那張圖,再貼一遍:

好這時候我們已經知道Set<String> names裏面是什麼了,就是匹配上ApplicationContextInitializer.class的具體類路徑,有類路徑了那就創建實例instances唄,看一下下面的createSpringFactoriesInstances()方法:

果然上篇文章提到的ClassUtils.forName()出現了,這個方法就是一個加載類再利用構造方法構建實例的過程,具體就不看了。

現在已經理清了getSpringFactoriesInstances()這個方法就是拿指定類的實例,還可能不止一個,現在可以回到SpringApplication的構造方法裏面了:

好吧,從上一篇到這裏居然只走了一行代碼,一個getSpringFactoriesInstances一直說到現在,不過應該的,這個方法裏面涉及到了類加載,還涉及到了springBoot的spring.factories配置文件機制,值得詳細總結一下。

現在拿到初始化器的實例後就是調用setInitializers()開始設置了,看一下這個方法:

還好沒有什麼幺蛾子,就是初始化好這個this.initializers,這就算初始化器設置好了,留着用唄,具體怎麼用就得看這個初始化器裏面是什麼了,這裏先放一放。

至於下面的setListeners()方法是設置監聽器,和setInitializers()一樣先拿到ApplicationListener的實例,setListeners()方法裏面也是這樣:

初始化器和監聽器是SpringBoot啓動過程中很重要的東西,這裏給他們設置好了,具體這兩個東西起着什麼作用 留着後面看。

SpringApplication的構造方法終於只剩一行了:

 推斷應用的main入口,可以看一下這個方法:

就是拿到了運行時的線程棧信息,然後判斷方法名是不是有一個叫"main"的,找到main方法就加載這個類,就這?

 到這裏SpringApplication的構造方法終於看完了:

這一行的前半句就足足寫了5篇,我還記得當初只是想知道args和java -jar的參數是什麼,不過接下來就是run方法了,離我想知道的答案應該不遠了。

最後照舊總結一下:這篇主要是介紹了初始化SpringApplication的時候如何設置初始化器和監聽器的,留了一個問題,就是這個初始化器和監聽器在啓動流程中起着什麼作用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章