引言
最近有個讀者在面試,面試中被問到了這樣一個問題“看你項目中用到了springboot
,你說下springboot
的自動配置是怎么實現的?”這應該是一個springboot
里面最最常見的一個面試題了。下面我們就來帶著這個問題一起解剖下springBoot
的自動配置原理吧。
SpringMvc和SpringBoot對比
首先我們回顧下原來搭建一個springmvc
的hello-word
的web
項目(xml
配置的)我們是不是要在pom
中導入各種依賴,然后各個依賴有可能還會存在版本沖突需要各種排除。當你歷盡千辛萬苦的把依賴解決了,然后還需要編寫web.xml、springmvc.xml
配置文件等。我們只想寫個hello-word
項目而已,確把一大把的時間都花在了配置文件和jar
包的依賴上面。大大的影響了我們開發的效率,以及加大了web
開發的難度。為了簡化這復雜的配置、以及各個版本的沖突依賴關系,springBoot
就應運而生。我們現在通過idea
創建一個springboot
項目只要分分鐘就解決了,你不需要關心各種配置(基本實現零配置)。讓你真正的實現了開箱即用。SpringBoot
幫你節約了大量的時間去陪女朋友,不對程序員怎么會有女朋友呢?(沒有的話也是可以new一個的)它的出現不僅可以讓你把更多的時間都花在你的業務邏輯開發上,而且還大大的降低了web
開發的門檻。所以SpringBoot
還是比較善解人衣的,錯啦錯啦是善解人意,知道開發人員的痛點在哪。
SpringBoot自動配置加載
既然Springboot
盡管這么好用,但是作為一個使用者,我們還是比較好奇它是怎么幫我們實現開箱即用的。Spring Boot
有一個全局配置文件:application.properties或application.yml
。在這個全局文件里面可以配置各種各樣的參數比如你想改個端口啦server.port
或者想調整下日志的級別啦通通都可以配置。更多其他可以配置的屬性可以參照官網。https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/reference/htmlsingle/#common-application-properties
這么多屬性,這些屬性在項目是怎么起作用的呢?
SpringBoot
項目看下來啥配置也沒有,配置”(application.properties或application.yml
除外),既 然從配置上面找不到突破口,那么我們就只能從啟動類上面找入口了。啟動類也就一個光禿禿的一個main
方法,類上面僅有一個注SpringBootApplication
這個注解是
Spring Boot
項目必不可少的注解。那么自動配置原理一定和這個注解有著千絲萬縷的聯系!我們下面來一起看看這個注解吧。@SpringBootApplication注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
這里最上面四個注解的話沒啥好說的,基本上自己實現過自定義注解的話,都知道分別是什么意思。
-
@SpringBootConfiguration
繼承自@Configuration
,二者功能也一致,標注當前類是配置類。 -
@ComponentScan
用于類或接口上主要是指定掃描路徑,跟Xml里面的<context:component-scan base-package="" />
配置一樣。springboot
如果不寫這個掃描路徑的話,默認就是啟動類的路徑。 @EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
這個注解我們重點看下AutoConfigurationImportSelector
這個類getCandidateConfigurations
這個方法里面通過SpringFactoriesLoader.loadFactoryNames()
掃描所有具有META-INF/spring.factories
的jar
包( spring.factories 我們可以理解成 Spring Boot
自己的 SPI
機制)。
spring-boot-autoconfigure-x.x.x.x.jar
里就有一個spring.factories文件。spring.factories
文件由一組一組的Key = value
的形式,其中一個key
是EnableAutoConfiguration類的全類名,而它的value是一個以AutoConfiguration
結尾的類名的列表,有redis、mq
等這些類名以逗號分隔。
我們在回到
getAutoConfigurationEntry
這個方法當執行完getCandidateConfigurations
這個方法的時候我們可以看到此時總共加載了127
個自動配置類。這些類難道都要加載進去嗎?
springboot
還是沒有那么傻的,它提倡的話是按需加載。
- 它會去掉重復的類
- 過濾掉我們配置了
exclude
注解的類下面配置就會過濾掉RestTemplateAutoConfiguration
這個類
在這里插入圖片描述 - 經過上面的處理,剩下的這寫自動配置的類如果要起作用的話,是需要滿足一定的條件的。這些條件的滿足的話
spring boot
是通過條件注解來實現的。
@ConditionalOnBean:當容器里有指定Bean的條件下
@ConditionalOnClass:當類路徑下有指定的類的條件下
@ConditionalOnExpression:基于SpEL表達式為true的時候作為判斷條件才去實例化
@ConditionalOnJava:基于JVM版本作為判斷條件
@ConditionalOnJndi:在JNDI存在的條件下查找指定的位置
@ConditionalOnMissingBean:當容器里沒有指定Bean的情況下
@ConditionalOnMissingClass:當容器里沒有指定類的情況下
@ConditionalOnWebApplication:當前項目時Web項目的條件下
@ConditionalOnNotWebApplication:當前項目不是Web項目的條件下
@ConditionalOnProperty:指定的屬性是否有指定的值
@ConditionalOnResource:類路徑是否有指定的值
@ConditionalOnOnSingleCandidate:當指定Bean在容器中只有一個,或者有多個但是指定首選的Bean
這些注解都組合了@Conditional
注解,只是使用了不同的條件組合最后為true時才會去實例化需要實例化的類,否則忽略過濾掉。我們在回到代碼可以看到經過了條件判斷過濾后我們剩下符合條件的自動配置類只剩23個了。其他的都是因為不滿足條件注解而被過濾了。
如果我們想知道哪些自動配置類被過濾了,是由于什么原因被過濾了,以及加載了哪些類等。
spring boot
都為我們記錄了日志。還是非常貼心的。我們可以調整下我們日志的級別改為debug
。然后我們就能看到以下日志了這里就截取了部分日志。總共分別有下面四部分日志:
-
Positive matches
:@Conditional
條件為真,配置類被Spring容器加載。 -
Negative matches:
@Conditional
條件為假,配置類未被Spring容器加載。 -
Exclusions
: 我們明確了不需要加載的類。比如在上面啟動類配置的RestTemplateAutoConfiguration
類 -
Unconditional classes
: 自動配置類不包含任何類級別的條件,也就是說,類始終會被自動加載。
自動配置生效
我們以ServletWebServerFactoryAutoConfiguration
配置類為例,解釋一下全局配置文件中的屬性如何生效,比如:server.port=88
,是如何生效的(當然不配置也會有默認值,這個默認值來自于org.apache.catalina.startup.Tomcat
)。
// 標記為配置類
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// 如果有ServletRequest.class 才會生效
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
// 把@ConfigurationProperties注解的類注入為Spring容器的Bean。
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
我們可以發現EnableConfigurationProperties
注解里面配置的ServerProperties.class
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
/**
* Server HTTP port.
*/
private Integer port;
在這個類上有一個注解:@ConfigurationProperties
,它的作用就是從配置文件中綁定屬性到對應的bean上(也就是把我們application.properties
對應的server.port映射到ServerProperties
類中的port
屬性)而@EnableConfigurationProperties
這個注解就是把已經綁定了屬性的bean
(ServerProperties
)注入到spring
容器中(相當于@Component
注解一樣)。
所有在配置文件中能配置的屬性都是在xxxxPropertites
類中封裝著,配置文件能配置什么就可以參照某個功能對應的這個屬性類。
到現在為止應該能回答文章開頭的那個問題了,面試的時候應該不需要回答的這么詳細可以參考下以下答案:
Spring Boot啟動的時候會通過@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自動配置類,并對其進行加載,而這些自動配置類都是以AutoConfiguration結尾來命名的,它實際上就是一個JavaConfig形式的Spring容器配置類,它能通過以Properties結尾命名的類中取得在全局配置文件中配置的屬性如:server.port,而XxxxProperties類是通過@ConfigurationProperties注解與全局配置文件中對應的屬性進行綁定的。
在網上找了一張圖,基本上把自動裝配的流程給說清楚了。
總結
-
SpringBoot
啟動會加載大量的自動配置類(通過“SPI
”的方式),然后會根據條件注解保留一些需要的類。 - 我們新引入一個組件,可以先看看springBoot是否已經有默認的提供。
-
SpringBoot
基本實現了“零配置“,并且開箱即用。
結束
- 由于自己才疏學淺,難免會有紕漏,假如你發現了錯誤的地方,還望留言給我指出來,我會對其加以修正。
- 如果你覺得文章還不錯,你的轉發、分享、贊賞、點贊、留言就是對我最大的鼓勵。
- 感謝您的閱讀,十分歡迎并感謝您的關注。
[圖片上傳失敗...(image-12b8e1-1595678476285)]
站在巨人的肩膀上摘蘋果:
https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/reference/htmlsingle
https://blog.csdn.net/u014745069/article/details/83820511
https://afoo.me/posts/2015-07-09-how-spring-boot-works.html