1. 程式人生 > >SpringBoot內建的各種Starter是怎樣構建的?--SpringBoot原始碼(六)

SpringBoot內建的各種Starter是怎樣構建的?--SpringBoot原始碼(六)

**注:該原始碼分析對應SpringBoot版本為2.1.0.RELEASE** # 1 溫故而知新 本篇接 [外部配置屬性值是如何被繫結到XxxProperties類屬性上的?--SpringBoot原始碼(五)](https://juejin.im/post/5e689b49e51d4527143e5e2f) 溫故而知新,我們來簡單回顧一下上篇的內容,上一篇我們分析了SpringBoot**外部配置屬性值是如何被繫結到XxxProperties類屬性上**的相關原始碼,現將外部屬性繫結的重要步驟總結如下: 1. 首先是`@EnableConfigurationProperties`註解`import`了`EnableConfigurationPropertiesImportSelector`後置處理器; 2. `EnableConfigurationPropertiesImportSelector`後置處理器又向`Spring`容器中註冊了`ConfigurationPropertiesBeanRegistrar`和`ConfigurationPropertiesBindingPostProcessorRegistrar`這兩個`bean`; 3. 其中`ConfigurationPropertiesBeanRegistrar`向`Spring`容器中註冊了`XxxProperties`型別的`bean`;`ConfigurationPropertiesBindingPostProcessorRegistrar`向`Spring`容器中註冊了`ConfigurationBeanFactoryMetadata`和`ConfigurationPropertiesBindingPostProcessor`兩個後置處理器; 4. `ConfigurationBeanFactoryMetadata`後置處理器在初始化`bean` `factory`時將`@Bean`註解的元資料儲存起來,以便在後續的外部配置屬性繫結的相關邏輯中使用; 5. `ConfigurationPropertiesBindingPostProcessor`後置處理器將外部配置屬性值繫結到`XxxProperties`類屬性的邏輯委託給`ConfigurationPropertiesBinder`物件,然後`ConfigurationPropertiesBinder`物件又最終將屬性繫結的邏輯委託給`Binder`物件來完成。 可見,重要的是上面的**第5步**。 # 2 引言 我們都知道,SpringBoot內建了各種`Starter`起步依賴,我們使用非常方便,大大減輕了我們的開發工作。有了`Starter`起步依賴,我們不用去考慮這個專案需要什麼庫,這個庫的`groupId`和`artifactId`是什麼?更不用擔心引入這個版本的庫後會不會跟其他依賴有沒有衝突。 > **舉個栗子**:現在我們想開發一個web專案,那麼只要引入`spring-boot-starter-web`這個起步依賴就可以了,不用考慮要引入哪些版本的哪些依賴了。像以前我們還要考慮引入哪些依賴庫,比如要引入`spring-web`和`spring-webmvc`依賴等;此外,還要考慮引入這些庫的哪些版本才不會跟其他庫衝突等問題。 那麼我們今天暫時不分析SpringBoot自動配置的原始碼,由於起步依賴跟自動配置的關係是如影隨形的關係,因此本篇先站在maven專案構建的角度來巨集觀分析下我們平時使用的**SpringBoot內建的各種`Starter`是怎樣構建的?** # 3 Maven傳遞依賴的optional標籤 在分析SpringBoot內建的各種`Starter`構建原理前,我們先來認識下Maven的`optional`標籤,因為這個標籤起到至關重要的作用。 Maven的`optional`標籤表示可選依賴即不可傳遞的意思,下面直接舉個栗子來說明。 比如有`A`,`B`和`C`三個庫,`C`依賴`B`,`B`依賴`A`。下面看下這三個庫的`pom.xml`檔案: ```java // A的pom.xml ``` ```java ``` ```java ``` 上面三個`A`,`B`和`C`庫的`pom.xml`可知,`B`庫依賴`A`庫,然後`C`庫又依賴了`B`庫,那麼請想一下,**Maven打包構建`C`庫後,`A`庫有沒有被引進來?** 答案肯定是**沒有**,因為`B`庫引入`A`庫依賴時使用了``,即將Maven的`optional`標籤值設為了`true`,此時`C`庫再引入`B`庫依賴時,`A`庫是不會被引入到`C`庫的。 同時跟Maven傳遞依賴有關的還有一個`exclusions`標籤,這個表示將某個庫的某個子依賴排除掉,這裡不再詳述。 # 4 SpringBoot內建的各種Starter是怎樣構建的? 我們現在來探究SpringBoot內建的各種`Starter`到底是怎樣構建的呢? 還記得[如何分析SpringBoot原始碼模組及結構?](https://juejin.im/post/5e521a2fe51d4526f55f014a)這篇文章分析的SpringBoot內部的模組之間的關係嗎?先來回顧一下SpringBoot原始碼內部模組圖: ![](https://user-gold-cdn.xitu.io/2020/3/14/170d85d8243e32e0?w=447&h=516&f=png&s=291414)
圖1
我們都知道,SpringBoot的`Starter`的構建的原理實質就是自動配置,因此由圖1可以看到SpringBoot原始碼專案內部跟`Starter`及其自動配置有關的模組有四個:`spring-boot-starters`,`spring-boot-actuator-autoconfigure`,`spring-boot-autoconfigure`和`spring-boot-test-autoconfigure`。 每個模組的作用請看[如何分析SpringBoot原始碼模組及結構?](https://juejin.im/post/5e521a2fe51d4526f55f014a)這篇文章,這裡不再贅述。 那麼,`spring-boot-starters`模組跟後面三個自動配置有關的模組`xxx-autoconfigure`模組的關係是怎樣的呢? 此時我們先來看看`spring-boot-starters`模組裡面的結構是怎樣的? ![](https://user-gold-cdn.xitu.io/2020/3/15/170db7f1bbb88508?w=429&h=743&f=png&s=164239)
圖2
由圖2可以看到`spring-boot-starters`模組包含了SpringBoot內建的各種`starter`:`spring-boot-starter-xxx`。由於SpringBoot內建的各種`starter`太多,以我們常用的`spring-boot-starter-web`起步依賴來探究好了。 我們首先看下`spring-boot-starter-web`模組內部結構: ![](https://user-gold-cdn.xitu.io/2020/3/15/170db907457d9085?w=503&h=139&f=png&s=8107)
圖3
可以看到`spring-boot-starter-web`模組裡面只有`.flattened-pom.xml`和`pom.xml`檔案,**而沒有任何程式碼**!有點出乎我們意料。我們都知道若要用到SpringBoot的web功能時引入`spring-boot-starter-web`起步依賴即可,而現在`spring-boot-starter-web`模組裡面沒有一行程式碼,那麼`spring-boot-starter-web`究竟是如何構建的呢?會不會跟圖1所示的`spring-boot-autoconfigure`自動配置模組有關? 此時我們就需要看下`spring-boot-starter-web`模組的`pom.xml`檔案內容: ![](https://user-gold-cdn.xitu.io/2020/3/15/170db9ac9d262916?w=1168&h=909&f=png&s=119294)
圖4
由圖4可以看到,`spring-boot-starter-web`模組依賴了`spring-boot-starter`,`spring-boot-starter-tomcat`,`spring-web`和`spring-webmvc`等模組,居然沒有依賴`spring-boot-autoconfigure`自動配置模組! 由於`spring-boot-starter-web`模組肯定跟`spring-boot-autoconfigure`自動配置模組有關,所以`spring-boot-starter-web`模組肯定是間接依賴了`spring-boot-autoconfigure`自動配置模組。 圖4標有標註"重點關注"的`spring-boot-starter`模組是絕大部分`spring-boot-starter-xxx`模組依賴的基礎模組,是核心的`Starter`,包括了自動配置,日誌和`YAML`支援。我們此時來關注下`spring-boot-starter`的`pom.xml`檔案,也許其依賴了了`spring-boot-autoconfigure`自動配置模組。 ![](https://user-gold-cdn.xitu.io/2020/3/15/170dbb8e85825a92?w=1147&h=916&f=png&s=119226)
圖5
由圖5可以看到,我們前面的猜想沒有錯,**正是`spring-boot-starter`模組依賴了`spring-boot-autoconfigure`自動配置模組!**因此,到了這裡我們就可以得出結論了:`spring-boot-starter-web`模組沒有一行程式碼,但是其通過`spring-boot-starter`模組**間接**依賴了`spring-boot-autoconfigure`自動配置模組,從而實現了其起步依賴的功能。 此時我們再來看下`spring-boot-autoconfigure`自動配置模組的內部包結構: ![](https://user-gold-cdn.xitu.io/2020/3/15/170dbc82e99ec94a?w=415&h=839&f=png&s=85066)
圖6
由圖6紅框處,我們可以知道`spring-boot-starter-web`起步依賴的自動配置功能原來是由`spring-boot-autoconfigure`模組的`web`包下的類實現的。 到了這裡`spring-boot-starter-web`起步依賴的構建基本原理我們就搞清楚了,但是還有一個特別重要的關鍵點我們還沒Get到。這個關鍵點跟Maven的`optional`標籤有的作用有關。 為了Get到這個點,我們先來思考一個問題:平時我們開發`web`專案為什麼引入了`spring-boot-starter-web`這個起步依賴後,`spring-boot-autoconfigure`模組的`web`相關的自動配置類就會起自動起作用呢? 我們應該知道,某個自動配置類起作用往往是由於`classpath`中存在某個類,這裡以`DispatcherServletAutoConfiguration`這個自動配置類為切入點去Get這個點好了。 先看下`DispatcherServletAutoConfiguration`能夠自動配置的條件是啥? ![](https://user-gold-cdn.xitu.io/2020/3/15/170dbd6188c1804b?w=702&h=194&f=png&s=26215)
圖7
由圖7所示,`DispatcherServletAutoConfiguration`能夠自動配置的條件之一是`@ConditionalOnClass(DispatcherServlet.class)`,即只有`classpath`中存在`DispatcherServlet.class`這個類,那麼`DispatcherServletAutoConfiguration`自動配置相關邏輯才能起作用。 而`DispatcherServlet`這個類是在`spring-webmvc`這個依賴庫中的,如下圖所示: ![](https://user-gold-cdn.xitu.io/2020/3/15/170dbdabe203fc0f?w=500&h=385&f=png&s=22361)
圖8
此時我們再看下`spring-boot-autoconfigure`模組的`pom.xml`檔案引入`spring-webmvc`這個依賴的情況: ![](https://user-gold-cdn.xitu.io/2020/3/15/170dbddef7bc7cdc?w=722&h=203&f=png&s=18827)
圖9
由圖9所示,`spring-boot-autoconfigure`模組引入的`spring-webmvc`這個依賴時`optional`被設定為`true`,原來是可選依賴。即`spring-webmvc`這個依賴庫只會被匯入到`spring-boot-autoconfigure`模組中,而不會被匯入到間接依賴`spring-boot-autoconfigure`模組的`spring-boot-starter-web`這個起步依賴中。 此時,我們再來看看`spring-boot-starter-web`的`pom.xml`檔案的依賴情況: ![](https://user-gold-cdn.xitu.io/2020/3/15/170dbeea30725cc1?w=944&h=748&f=png&s=91061)
圖10
由圖10所示,`spring-boot-starter-web`起步依賴**顯式**引入了`spring-webmvc`這個依賴庫,即引入`spring-webmvc` 時沒有`optional`這個標籤,又因為`DispatcherServlet`這個類是在`spring-webmvc`這個依賴庫中的,從而`classpath`中存在`DispatcherServlet`這個類,因此`DispatcherServletAutoConfiguration`這個自動配置類就生效了。當然,`web`相關的其他自動配置類生效也是這個原理。 至此,我們也明白了`spring-boot-autoconfigure`模組為什麼要把引入的`spring-webmvc`這個依賴作為可選依賴了,其目的就是為了在`spring-boot-starter-web`起步依賴中能顯式引入`spring-webmvc`這個依賴(這個起決定性作用),從而我們開發web專案只要引入了`spring-boot-starter-web`起步依賴,那麼web相關的自動配置類就生效,從而可以開箱即用​這個就是`spring-boot-starter-web`這個起步依賴的構建原理了。 前面提到的`spring-boot-starter-actuator`,`spring-boot-starter-test`及其他內建的`spring-boot-starter-xxx`的起步依賴的構建原理也是如此,只不過`spring-boot-starter-actuator`依賴的是`spring-boot-actuator-autoconfigure`,`spring-boot-starter-test`依賴的是`spring-boot-test-autoconfigure`模組罷了,這裡不再詳述。 > **思考**:`spring-boot-actuator-autoconfigure`的`pom.xml`檔案引入了20多個可選依賴,而為什麼`spring-boot-starter-actuator`起步依賴只引入了`micrometer-core`這個依賴呢? # 5 模仿SpringBoot包結構自定義一個Starter 前面分析了SpringBoot內建的各種`Starter`的構建原理,理論聯絡實踐,那麼如果能夠動手實踐一下自定義`Starter`那就更好了。 下面提供一個自定義`Starter`的一個簡單`Demo`,這個`Demo`完全模仿`SpringBoot`內建`Starter`的內部包結構來編寫,對於進一步瞭解SpringBoot內建的各種`Starter`的構建原理很有幫助。 下面是這個`Demo`的github地址,推薦給有興趣的小夥伴們。 [模仿springboot內部結構自定義Starter](https://github.com/jinyue233/mock-spring-boot-autoconfiguration)。此外,如何自定義一個`Starter`,可以參考下Mybatis的[spring-boot-starter](https://github.com/mybatis/spring-boot-starter)是如何編寫的。 # 6 小結 好了,SpringBoot內建的各種`Starter`的構建原理分析就到此結束了,現將關鍵點總結下: 1. `spring-boot-starter-xxx`起步依賴沒有一行程式碼,而是直接或間接依賴了`xxx-autoconfigure`模組,而`xxx-autoconfigure`模組承擔了`spring-boot-starter-xxx`起步依賴自動配置的實現; 2. `xxx-autoconfigure`自動配置模組引入了一些可選依賴,這些可選依賴不會被傳遞到`spring-boot-starter-xxx`起步依賴中,這是起步依賴構建的**關鍵點**; 3. `spring-boot-starter-xxx`起步依賴**顯式**引入了一些對自動配置起作用的可選依賴; 4. 經過前面3步的準備,我們專案只要引入了某個起步依賴後,就可以開箱即用了,而不用手動去建立一些`bean`等。 **原創不易,幫忙點個讚唄!** 由於筆者水平有限,若文中有錯誤還請指出,謝謝。 參考: 1,[Maven 依賴傳遞性透徹理解](https://dayarch.top/p/maven-dependency-optional-transitive.html) --------------------------------------------------- 歡迎關注【原始碼筆記】公眾號,一起學習交流。 ![](https://user-gold-cdn.xitu.io/2020/3/15/170dd9bb2b5b59de?w=142&h=135&f=png&