注:該源碼分析對應SpringBoot版本為2.1.0.RELEASE
1 溫故而知新
本篇接 外部配置屬性值是如何被綁定到XxxProperties類屬性上的?--SpringBoot源碼(五)
溫故而知新,我們來簡單回顧一下上篇的內容,上一篇我們分析了SpringBoot外部配置屬性值是如何被綁定到XxxProperties類屬性上的相關源碼,現將外部屬性綁定的重要步驟總結如下:
- 首先是
@EnableConfigurationProperties注解import了EnableConfigurationPropertiesImportSelector后置處理器; EnableConfigurationPropertiesImportSelector后置處理器又向Spring容器中注冊了ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar這兩個bean;- 其中
ConfigurationPropertiesBeanRegistrar向Spring容器中注冊了XxxProperties類型的bean;ConfigurationPropertiesBindingPostProcessorRegistrar向Spring容器中注冊了ConfigurationBeanFactoryMetadata和ConfigurationPropertiesBindingPostProcessor兩個后置處理器; ConfigurationBeanFactoryMetadata后置處理器在初始化beanfactory時將@Bean注解的元數據存儲起來,以便在后續的外部配置屬性綁定的相關邏輯中使用;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文件:
// A的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>com.ymbj</groupId>
<artifactId>A</artifactId>
<version>1.0-SNAPSHOT</version>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>com.ymbj</groupId>
<artifactId>B</artifactId>
<version>1.0-SNAPSHOT</version>
<!--注意是可選依賴-->
<dependencies>
<dependency>
<groupId>com.ymbj</groupId>
<artifactId>A</artifactId>
<version>1.0-SNAPSHOT</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>com.ymbj</groupId>
<artifactId>C</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.ymbj</groupId>
<artifactId>B</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
上面三個A,B和C庫的pom.xml可知,B庫依賴A庫,然后C庫又依賴了B庫,那么請想一下,Maven打包構建C庫后,A庫有沒有被引進來?
答案肯定是沒有,因為B庫引入A庫依賴時使用了<optional>true</optional>,即將Maven的optional標簽值設為了true,此時C庫再引入B庫依賴時,A庫是不會被引入到C庫的。
同時跟Maven傳遞依賴有關的還有一個exclusions標簽,這個表示將某個庫的某個子依賴排除掉,這里不再詳述。
4 SpringBoot內置的各種Starter是怎樣構建的?
我們現在來探究SpringBoot內置的各種Starter到底是怎樣構建的呢?
還記得如何分析SpringBoot源碼模塊及結構?這篇文章分析的SpringBoot內部的模塊之間的關系嗎?先來回顧一下SpringBoot源碼內部模塊圖:

我們都知道,SpringBoot的Starter的構建的原理實質就是自動配置,因此由圖1可以看到SpringBoot源碼項目內部跟Starter及其自動配置有關的模塊有四個:spring-boot-starters,spring-boot-actuator-autoconfigure,spring-boot-autoconfigure和spring-boot-test-autoconfigure。 每個模塊的作用請看如何分析SpringBoot源碼模塊及結構?這篇文章,這里不再贅述。
那么,spring-boot-starters模塊跟后面三個自動配置有關的模塊xxx-autoconfigure模塊的關系是怎樣的呢?
此時我們先來看看spring-boot-starters模塊里面的結構是怎樣的?

由圖2可以看到spring-boot-starters模塊包含了SpringBoot內置的各種starter:spring-boot-starter-xxx。由於SpringBoot內置的各種starter太多,以我們常用的spring-boot-starter-web起步依賴來探究好了。
我們首先看下spring-boot-starter-web模塊內部結構:

可以看到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文件內容:

由圖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自動配置模塊。

由圖5可以看到,我們前面的猜想沒有錯,正是spring-boot-starter模塊依賴了spring-boot-autoconfigure自動配置模塊!因此,到了這里我們就可以得出結論了:spring-boot-starter-web模塊沒有一行代碼,但是其通過spring-boot-starter模塊間接依賴了spring-boot-autoconfigure自動配置模塊,從而實現了其起步依賴的功能。
此時我們再來看下spring-boot-autoconfigure自動配置模塊的內部包結構:

由圖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能夠自動配置的條件是啥?

由圖7所示,DispatcherServletAutoConfiguration能夠自動配置的條件之一是@ConditionalOnClass(DispatcherServlet.class),即只有classpath中存在DispatcherServlet.class這個類,那么DispatcherServletAutoConfiguration自動配置相關邏輯才能起作用。
而DispatcherServlet這個類是在spring-webmvc這個依賴庫中的,如下圖所示:

此時我們再看下spring-boot-autoconfigure模塊的pom.xml文件引入spring-webmvc這個依賴的情況:

由圖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文件的依賴情況:

由圖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。此外,如何自定義一個Starter,可以參考下Mybatis的spring-boot-starter是如何編寫的。
6 小結
好了,SpringBoot內置的各種Starter的構建原理分析就到此結束了,現將關鍵點總結下:
spring-boot-starter-xxx起步依賴沒有一行代碼,而是直接或間接依賴了xxx-autoconfigure模塊,而xxx-autoconfigure模塊承擔了spring-boot-starter-xxx起步依賴自動配置的實現;xxx-autoconfigure自動配置模塊引入了一些可選依賴,這些可選依賴不會被傳遞到spring-boot-starter-xxx起步依賴中,這是起步依賴構建的關鍵點;spring-boot-starter-xxx起步依賴顯式引入了一些對自動配置起作用的可選依賴;- 經過前面3步的准備,我們項目只要引入了某個起步依賴后,就可以開箱即用了,而不用手動去創建一些
bean等。
原創不易,幫忙點個贊唄!
由於筆者水平有限,若文中有錯誤還請指出,謝謝。
參考:
1,Maven 依賴傳遞性透徹理解
歡迎關注【源碼筆記】公眾號,一起學習交流。

