javascript
芋道 Spring Boot 自动配置原理
轉載自? 芋道 Spring Boot 自動配置原理
1. 概述
友情提示:因為本文是分享 Spring Boot 自動配置的原理,所以需要胖友有使用過 Spring Boot 的經驗。如果還沒使用過的胖友,不用慌,先跳轉到《芋道 Spring Boot SpringMVC 入門》文章,將前兩節閱讀完,感受下 Spring Boot 的魅力。
Spring Boot 自動配置,顧名思義,是希望能夠自動配置,將我們從配置的苦海中解脫出來。那么既然要自動配置,它需要解三個問題:
-
滿足什么樣的條件?
-
創建哪些?Bean?
-
創建的 Bean 的屬性?
我們來舉個示例,對照下這三個問題。在我們引入?spring-boot-starter-web?依賴,會創建一個 8080 端口的內嵌 Tomcat,同時可以通過?application.yaml?配置文件中的?server.port?配置項自定義端口。那么這三個問題的答案如下:
友情提示:為了更易懂,這里的答案暫時是表象的,不絕對精準。
-
滿足什么樣的條件?因為我們引入了?spring-boot-starter-web?依賴。
-
創建哪些?Bean?創建了一個內嵌的 Tomcat Bean,并進行啟動。
-
創建的 Bean 的屬性?通過?application.yaml?配置文件的?server.port?配置項,定義 Tomcat Bean 的啟動端口屬性,并且默認值為 8080。
壯著膽子,我們來看看 Spring Boot 提供的 EmbeddedWebServerFactoryCustomizerAutoConfiguration 類,負責創建內嵌的 Tomcat、Jetty 等等 Web 服務器的配置類。代碼如下:
@Configuration?//?<1.1> @ConditionalOnWebApplication?//?<2.1> @EnableConfigurationProperties(ServerProperties.class)?//?<3.1> public?class??EmbeddedWebServerFactoryCustomizerAutoConfiguration?{/***?Nested?configuration?if?Tomcat?is?being?used.*/@Configuration?//?<1.2>@ConditionalOnClass({?Tomcat.class,?UpgradeProtocol.class?})public?static?class?TomcatWebServerFactoryCustomizerConfiguration?{@Beanpublic?TomcatWebServerFactoryCustomizer?tomcatWebServerFactoryCustomizer(Environment?environment,?ServerProperties?serverProperties)?{//?<3.2>return?new?TomcatWebServerFactoryCustomizer(environment,?serverProperties);}}/***?Nested?configuration?if?Jetty?is?being?used.*/@Configuration?//?<1.3>@ConditionalOnClass({?Server.class,?Loader.class,?WebAppContext.class?})public?static?class?JettyWebServerFactoryCustomizerConfiguration?{@Beanpublic?JettyWebServerFactoryCustomizer?jettyWebServerFactoryCustomizer(Environment?environment,?ServerProperties?serverProperties)?{//?<3.3>return?new?JettyWebServerFactoryCustomizer(environment,?serverProperties);}}/***?Nested?configuration?if?Undertow?is?being?used.*///?...?省略?UndertowWebServerFactoryCustomizerConfiguration?代碼/***?Nested?configuration?if?Netty?is?being?used.*///?...?省略?NettyWebServerFactoryCustomizerConfiguration?代碼}在開始看代碼之前,我們先來簡單科普下 Spring JavaConfig 的小知識。在 Spring3.0 開始,Spring 提供了 JavaConfig 的方式,允許我們使用 Java 代碼的方式,進行 Spring Bean 的創建。示例代碼如下:
@Configuration public?class?DemoConfiguration?{@Beanpublic?void?object()?{return?new?Obejct();}}-
通過在類上添加?@Configuration?注解,聲明這是一個 Spring 配置類。
-
通過在方法上添加?@Bean?注解,聲明該方法創建一個 Spring Bean。
OK,現在我們在回過頭看看 EmbeddedWebServerFactoryCustomizerAutoConfiguration 的代碼,我們分成三塊內容來講,剛好解決我們上面說的三個問題:
-
① 配置類
-
② 條件注解
-
③ 配置屬性
① 配置類
<1.1>?處,在類上添加了?@Configuration?注解,聲明這是一個配置類。因為它的目的是自動配置,所以類名以 AutoConfiguration 作為后綴。
<1.2>、<1.3>?處,分別是用于初始化 Tomcat、Jetty 相關 Bean 的配置類。
-
TomcatWebServerFactoryCustomizerConfiguration 配置類,負責創建 TomcatWebServerFactoryCustomizer Bean,從而初始化內嵌的 Tomcat 并進行啟動。
-
JettyWebServerFactoryCustomizer 配置類,負責創建 JettyWebServerFactoryCustomizer Bean,從而初始化內嵌的 Jetty 并進行啟動。
如此,我們可以得到結論一,通過?@Configuration?注解的配置類,可以解決“創建哪些 Bean”的問題。
實際上,Spring Boot 的 spring-boot-autoconfigure 項目,提供了大量框架的自動配置類,稍后我們在「2. 自動配置類」小節詳細展開。
② 條件注解
<2>?處,在類上添加了?@ConditionalOnWebApplication?條件注解,表示當前配置類需要在當前項目是 Web 項目的條件下,才能生效。在 Spring Boot 項目中,會將項目類型分成 Web 項目(使用 SpringMVC 或者 WebFlux)和非 Web 項目。這樣我們就很容易理解,為什么 EmbeddedWebServerFactoryCustomizerAutoConfiguration 配置類會要求在項目類型是 Web 項目,只有 Web 項目才有必要創建內嵌的 Web 服務器呀。
<2.1>、<2.2>?處,在類上添加了?@ConditionalOnClass?條件注解,表示當前配置類需要在當前項目有指定類的條件下,才能生效。
-
TomcatWebServerFactoryCustomizerConfiguration 配置類,需要有?tomcat-embed-core?依賴提供的 Tomcat、UpgradeProtocol 依賴類,才能創建內嵌的 Tomcat 服務器。
-
JettyWebServerFactoryCustomizer 配置類,需要有?jetty-server?依賴提供的 Server、Loader、WebAppContext 類,才能創建內嵌的 Jetty 服務器。
如此,我們可以得到結論二,通過條件注解,可以解決“滿足什么樣的條件?”的問題。
實際上,Spring Boot 的?condition?包下,提供了大量的條件注解,稍后我們在「2. 條件注解」小節詳細展開。
③ 配置屬性
<3.1>?處,使用?@EnableConfigurationProperties?注解,讓 ServerProperties?配置屬性類生效。在 Spring Boot 定義了?@ConfigurationProperties?注解,用于聲明配置屬性類,將指定前綴的配置項批量注入到該類中。例如 ServerProperties 代碼如下:
@ConfigurationProperties(prefix?=?"server",?ignoreUnknownFields?=?true) public?class?ServerPropertiesimplements?EmbeddedServletContainerCustomizer,?EnvironmentAware,?Ordered?{/***?Server?HTTP?port.*/private?Integer?port;/***?Context?path?of?the?application.*/private?String?contextPath;//?...?省略其它屬性}-
通過?@ConfigurationProperties?注解,聲明將?server?前綴的配置項,設置到 ServerProperties 配置屬性類中。
<3.2>、<3.3>?處,在創建 TomcatWebServerFactoryCustomizer 和 JettyWebServerFactoryCustomizer 對象時,都會將 ServerProperties 傳入其中,作為后續創建的 Web 服務器的配置。也就是說,我們通過修改在配置文件的配置項,就可以自定義 Web 服務器的配置。
如此,我們可以得到結論三,通過配置屬性,可以解決“創建的 Bean 的屬性?”的問題。
🐶 至此,我們已經比較清晰的理解 Spring Boot 是怎么解決我們上面提出的三個問題,但是這樣還是無法實現自動配置。例如說,我們引入的?spring-boot-starter-web?等依賴,Spring Boot 是怎么知道要掃碼哪些配置類的。下面,繼續我們的旅途,繼續抽絲剝繭。
2. 自動配置類
在 Spring Boot 的 spring-boot-autoconfigure 項目,提供了大量框架的自動配置,如下圖所示:
在我們通過?SpringApplication#run(Class<?> primarySource, String... args)?方法,啟動 Spring Boot 應用的時候,有個非常重要的組件 SpringFactoriesLoader 類,會讀取?META-INF?目錄下的?spring.factories?文件,獲得每個框架定義的需要自動配置的配置類。
我們以 spring-boot-autoconfigure 項目的 Spring Boot?spring.factories?文件來舉個例子,如下圖所示:
如此,原先?@Configuration?注解的配置類,就升級成類自動配置類。這樣,Spring Boot 在獲取到需要自動配置的配置類后,就可以自動創建相應的 Bean,完成自動配置的功能。
旁白君:這里其實還有一個非常有意思的話題,作為拓展知識,胖友可以后續去看看。實際上,我們可以把?spring.factories?理解成 Spring Boot 自己的 SPI 機制。感興趣的胖友,可以看看如下的文章:
-
《Spring Boot 的 SPI 機制》
-
《Java 的 SPI 機制》
-
《Dubbo 的 SPI 機制》
實際上,自動配置只是 Spring Boot 基于?spring.factories?的一個拓展點 EnableAutoConfiguration。我們從上圖中,還可以看到如下的拓展點:
-
ApplicationContextInitializer
-
ApplicationListener
-
AutoConfigurationImportListener
-
AutoConfigurationImportFilter
-
FailureAnalyzer
-
TemplateAvailabilityProvider
因為 spring-boot-autoconfigure 項目提供的是它選擇的主流框架的自動配置,所以其它框架需要自己實現。例如說,Dubbo 通過 dubbo-spring-boot-project 項目,提供 Dubbo 的自動配置。如下圖所示:
3. 條件注解
條件注解并不是 Spring Boot 所獨有,而是在 Spring3.1 版本時,為了滿足不同環境注冊不同的 Bean ,引入了?@Profile?注解。示例代碼如下:
@Configuration public?class?DataSourceConfiguration?{@Bean@Profile("DEV")public?DataSource?devDataSource()?{//?...?單機?MySQL}@Bean@Profile("PROD")public?DataSource?prodDataSource()?{//?...?集群?MySQL}}-
在測試環境下,我們注冊單機 MySQL 的 DataSource Bean。
-
在生產環境下,我們注冊集群 MySQL 的 DataSource Bean。
在 Spring4 版本時,提供了?@Conditional?注解,用于聲明在配置類或者創建 Bean 的方法上,表示需要滿足指定條件才能生效。示例代碼如下:
@Configuration public?class?TestConfiguration?{@Bean@Conditional(XXXCondition.class)public?Object?xxxObject()?{return?new?Object();}}-
其中,XXXCondition 需要我們自己實現 Condition 接口,提供具體的條件實現。
顯然,Spring4 提交的?@Conditional?注解非常不方便,需要我們自己去拓展。因此,Spring Boot 進一步增強,提供了常用的條件注解:
-
@ConditionalOnBean:當容器里有指定 Bean 的條件下
-
@ConditionalOnMissingBean:當容器里沒有指定 Bean 的情況下
-
@ConditionalOnSingleCandidate:當指定 Bean 在容器中只有一個,或者雖然有多個但是指定首選 Bean
-
@ConditionalOnClass:當類路徑下有指定類的條件下
-
@ConditionalOnMissingClass:當類路徑下沒有指定類的條件下
-
@ConditionalOnProperty:指定的屬性是否有指定的值
-
@ConditionalOnResource:類路徑是否有指定的值
-
@ConditionalOnExpression:基于 SpEL 表達式作為判斷條件
-
@ConditionalOnJava:基于 Java 版本作為判斷條件
-
@ConditionalOnJndi:在 JNDI 存在的條件下差在指定的位置
-
@ConditionalOnNotWebApplication:當前項目不是 Web 項目的條件下
-
@ConditionalOnWebApplication:當前項目是 Web項 目的條件下
4. 配置屬性
Spring Boot 約定讀取?application.yaml、application.properties?等配置文件,從而實現創建 Bean 的自定義屬性配置,甚至可以搭配?@ConditionalOnProperty?注解來取消 Bean 的創建。
咳咳咳,貌似這個小節沒有太多可以分享的內容,更多胖友可以閱讀《芋道 Spring Boot 配置文件入門》文章。
5. 內置 Starter
我們在使用 Spring Boot 時,并不會直接引入?spring-boot-autoconfigure?依賴,而是使用 Spring Boot 內置提供的 Starter 依賴。例如說,我們想要使用 SpringMVC 時,引入的是?spring-boot-starter-web?依賴。這是為什么呢?
因為 Spring Boot 提供的自動配置類,基本都有?@ConditionalOnClass?條件注解,判斷我們項目中存在指定的類,才會創建對應的 Bean。而擁有指定類的前提,一般是需要我們引入對應框架的依賴。
因此,在我們引入?spring-boot-starter-web?依賴時,它會幫我們自動引入相關依賴,從而保證自動配置類能夠生效,創建對應的 Bean。如下圖所示:
Spring Boot 內置了非常多的 Starter,方便我們引入不同框架,并實現自動配置。如下圖所示:
6. 自定義 Starter
在一些場景下,我們需要自己實現自定義 Starter 來達到自動配置的目的。例如說:
-
三方框架并沒有提供 Starter,比如說 Swagger、XXL-JOB 等。
-
Spring Boot 內置的 Starter 無法滿足自己的需求,比如說?spring-boot-starter-jdbc?不提供多數據源的配置。
-
隨著項目越來越大,想要提供適合自己團隊的 Starter 來方便配置項目,比如說永輝彩食鮮 csx-bsf-all 項目。
下面,我們一起來實現一個自定義 Starter,實現一個 Java 內置 HttpServer 服務器的自動化配置。最終項目如下圖所示:
在開始示例之前,我們要了解下 Spring Boot Starter 的命名規則,顯得我們更加專業(裝逼)。命名規則如下:
| Spring Boot 內置?Starter | spring-boot-starter-{框架} | spring-boot-starter-web |
| 框架?自定義?Starter | {框架}-spring-boot-starter | mybatis-spring-boot-starter |
| 公司?自定義?Starter | {公司}-spring-boot-starter-{框架} | 暫無,艿艿自己的想法哈 |
6.1 yunai-server-spring-boot-starter 項目
創建 yunai-server-spring-boot-starter 項目,實現一個 Java 內置 HttpServer 服務器的自動化配置??紤]到示例比較簡單,我們就不像 Spring Boot 拆分成?spring-boot-autoconfigure?和?spring-boot-starter-{框架}?兩個項目。
6.1.1 引入依賴
在?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"><parent><artifactId>lab-47</artifactId><groupId>cn.iocoder.springboot.labs</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>yunai-server-spring-boot-starter</artifactId><dependencies><!--?引入?Spring?Boot?Starter?基礎庫?--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.2.2.RELEASE</version></dependency></dependencies></project>6.1.2 YunaiServerProperties
在?cn.iocoder.springboot.lab47.yunaiserver.autoconfigure?包下,創建 YunaiServerProperties 配置屬性類,讀取?yunai.server?前綴的配置項。代碼如下:
@ConfigurationProperties(prefix?=?"yunai.server") public?class?YunaiServerProperties?{/***?默認端口*/private?static?final?Integer?DEFAULT_PORT?=?8000;/***?端口*/private?Integer?port?=?DEFAULT_PORT;public?static?Integer?getDefaultPort()?{return?DEFAULT_PORT;}public?Integer?getPort()?{return?port;}public?YunaiServerProperties?setPort(Integer?port)?{this.port?=?port;return?this;}}6.1.3 YunaiServerAutoConfiguration
在?cn.iocoder.springboot.lab47.yunaiserver.autoconfigure?包下,創建 YunaiServerAutoConfiguration 自動配置類,在項目中存在?com.sun.net.httpserver.HttpServer?類時,創建 HttpServer Bean,并啟動該服務器。代碼如下:
@Configuration?//?聲明配置類 @EnableConfigurationProperties(YunaiServerProperties.class)?//?使?YunaiServerProperties?配置屬性類生效 public?class?YunaiServerAutoConfiguration?{private?Logger?logger?=?LoggerFactory.getLogger(YunaiServerAutoConfiguration.class);@Bean?//?聲明創建?Bean@ConditionalOnClass(HttpServer.class)?//?需要項目中存在 com.sun.net.httpserver.HttpServer 類。該類為 JDK 自帶,所以一定成立。public?HttpServer?httpServer(YunaiServerProperties?serverProperties)?throws?IOException?{//?創建?HttpServer?對象,并啟動HttpServer?server?=?HttpServer.create(new?InetSocketAddress(serverProperties.getPort()),?0);server.start();logger.info("[httpServer][啟動服務器成功,端口為:{}]",?serverProperties.getPort());//?返回return?server;}}-
代碼比較簡單,胖友看看艿艿在代碼上添加的注釋喲。
6.1.4 spring.factories
在?resources?目錄下創建,創建?META-INF?目錄,然后在該目錄下創建?spring.factories?文件,添加自動化配置類為 YunaiServerAutoConfiguration。內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ cn.iocoder.springboot.lab47.yunaiserver.autoconfigure.YunaiServerAutoConfiguration至此,我們已經完成了一個自定義的 Starter。下面,我們在「6.2 lab-47-demo 項目」中引入,然后進行測試。
6.2 lab-47-demo 項目
創建 lab-47-demo 項目,引入我們自定義 Starter。
6.2.1 引入依賴
在?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"><parent><artifactId>lab-47</artifactId><groupId>cn.iocoder.springboot.labs</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>lab-47-demo</artifactId><dependencies><!--?引入自定義?Starter?--><dependency><groupId>cn.iocoder.springboot.labs</groupId><artifactId>yunai-server-spring-boot-starter</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies></project>6.2.2 配置文件
在?resource?目錄下,創建?application.yaml?配置文件,設置?yunai.server.port?配置項來自定義 HttpServer 端口。配置如下:
yunai:server:port:?8888?#?自定義?HttpServer?端口6.2.3 DemoApplication
創建?DemoApplication.java?類,配置?@SpringBootApplication?注解即可。代碼如下:
@SpringBootApplication public?class?DemoApplication?{public?static?void?main(String[]?args)?{SpringApplication.run(DemoApplication.class,?args);}}6.2.4 簡單測試
執行?DemoApplication#main(String[] args)?方法,啟動 Spring Boot 應用。打印日志如下:
2020-02-02?13:03:12.156??INFO?76469?---?[???????????main]?c.i.s.lab47.demo.DemoApplication?????????:?Starting?DemoApplication?on?MacBook-Pro-8?with?PID?76469?(/Users/yunai/Java/SpringBoot-Labs/lab-47/lab-47-demo/target/classes?started?by?yunai?in?/Users/yunai/Java/SpringBoot-Labs) 2020-02-02?13:03:12.158??INFO?76469?---?[???????????main]?c.i.s.lab47.demo.DemoApplication?????????:?No?active?profile?set,?falling?back?to?default?profiles:?default 2020-02-02?13:03:12.873??INFO?76469?---?[???????????main]?c.i.s.l.y.a.YunaiServerAutoConfiguration?:?[httpServer][啟動服務器成功,端口為:8888] 2020-02-02?13:03:12.927??INFO?76469?---?[???????????main]?c.i.s.lab47.demo.DemoApplication?????????:?Started?DemoApplication?in?1.053?seconds?(JVM?running?for?1.47)-
YunaiServerAutoConfiguration 成功自動配置 HttpServer Bean,并啟動該服務器在 8888 端口。
此時,我們使用瀏覽器訪問 http://127.0.0.1:8888/ 地址,返回結果為 404 Not Found。因為我們沒有給 HttpServer 相應的 Handler。
總結
以上是生活随笔為你收集整理的芋道 Spring Boot 自动配置原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为完成 5G-A 通感验证,首测微形变
- 下一篇: 下一站别离剧情介绍 电视剧下一站别离剧情