1. 程式人生 > 實用技巧 >springBoot自動裝配的原理你能說出來嗎?

springBoot自動裝配的原理你能說出來嗎?

最近有個讀者在面試,面試中被問到了這樣一個問題“看你專案中用到了springboot,你說下springboot的自動配置是怎麼實現的?”這應該是一個springboot裡面最最常見的一個面試題了。下面我們就來帶著這個問題一起解剖下springBoot的自動配置原理吧。

SpringMvc和SpringBoot對比

首先我們回顧下原來搭建一個springmvchello-wordweb專案(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@interfaceSpringBootApplication{

這裡最上面四個註解的話沒啥好說的,基本上自己實現過自定義註解的話,都知道分別是什麼意思。

  • @SpringBootConfiguration繼承自@Configuration,二者功能也一致,標註當前類是配置類。
  • @ComponentScan用於類或介面上主要是指定掃描路徑,跟Xml裡面的<context:component-scan base-package="" />配置一樣。springboot如果不寫這個掃描路徑的話,預設就是啟動類的路徑。
  • @EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public@interfaceEnableAutoConfiguration{

這個註解我們重點看下AutoConfigurationImportSelector這個類getCandidateConfigurations這個方法裡面通過SpringFactoriesLoader.loadFactoryNames()掃描所有具有META-INF/spring.factoriesjar包( 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})
publicclassServletWebServerFactoryAutoConfiguration{

我們可以發現EnableConfigurationProperties註解裡面配置的ServerProperties.class

@ConfigurationProperties(prefix="server",ignoreUnknownFields=true)
publicclassServerProperties{

/**
*ServerHTTPport.
*/
privateIntegerport;

在這個類上有一個註解:@ConfigurationProperties,它的作用就是從配置檔案中繫結屬性到對應的bean上(也就是把我們application.properties對應的server.port對映到ServerProperties類中的port屬性)而@EnableConfigurationProperties這個註解就是把已經綁定了屬性的beanServerProperties)注入到spring容器中(相當於@Component註解一樣)。所有在配置檔案中能配置的屬性都是在xxxxPropertites類中封裝著,配置檔案能配置什麼就可以參照某個功能對應的這個屬性類。到現在為止應該能回答文章開頭的那個問題了,面試的時候應該不需要回答的這麼詳細可以參考下以下答案:

Spring Boot啟動的時候會通過@EnableAutoConfiguration註解找到META-INF/spring.factories配置檔案中的所有自動配置類,並對其進行載入,而這些自動配置類都是以AutoConfiguration結尾來命名的,它實際上就是一個JavaConfig形式的Spring容器配置類,它能通過以Properties結尾命名的類中取得在全域性配置檔案中配置的屬性如:server.port,而XxxxProperties類是通過@ConfigurationProperties註解與全域性配置檔案中對應的屬性進行繫結的。

在網上找了一張圖,基本上把自動裝配的流程給說清楚了。圖片地址

總結

  • SpringBoot啟動會載入大量的自動配置類(通過“SPI”的方式),然後會根據條件註解保留一些需要的類。
  • 我們新引入一個元件,可以先看看springBoot是否已經有預設的提供。
  • SpringBoot基本實現了“零配置“,並且開箱即用。

結束

  • 由於自己才疏學淺,難免會有紕漏,假如你發現了錯誤的地方,還望留言給我指出來,我會對其加以修正。
  • 如果你覺得文章還不錯,你的轉發、分享、讚賞、點贊、留言就是對我最大的鼓勵。
  • 感謝您的閱讀,十分歡迎並感謝您的關注。

摘自:https://mp.weixin.qq.com/s/TnofWzuaH-WDcfYVfp-UoA