阿里面試:看你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 @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.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:
在這里插入圖片描述

這里就截取了部分日志。總共分別有下面四部分日志:

  • 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這個注解就是把已經綁定了屬性的beanServerProperties)注入到spring容器中(相當于@Component注解一樣)。
所有在配置文件中能配置的屬性都是在xxxxPropertites類中封裝著,配置文件能配置什么就可以參照某個功能對應的這個屬性類。
到現在為止應該能回答文章開頭的那個問題了,面試的時候應該不需要回答的這么詳細可以參考下以下答案:

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

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


圖片來源https://afoo.me/posts/2015-07-09-how-spring-boot-works.html

總結

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

結束

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。